straymark-cli 3.12.3

CLI for StrayMark — the cognitive discipline your AI-assisted projects need
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Clear, Paragraph, Widget};

use crate::tui::i18n_strings::t;
use crate::tui::theme;

pub struct HelpPopup<'a> {
    language: &'a str,
}

impl<'a> HelpPopup<'a> {
    pub fn new(language: &'a str) -> Self {
        Self { language }
    }
}

impl Widget for HelpPopup<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let popup = centered_rect(65, 80, area);
        let lang = self.language;

        Clear.render(popup, buf);

        let title = format!(" {} ", t("Keyboard Shortcuts", lang));
        let block = Block::default()
            .title(title)
            .title_style(
                Style::default()
                    .fg(theme::ACCENT)
                    .add_modifier(Modifier::BOLD),
            )
            .borders(Borders::ALL)
            .border_type(theme::BORDER_TYPE)
            .border_style(Style::default().fg(theme::ACCENT))
            .style(Style::default().bg(theme::SURFACE));

        let key_style = Style::default()
            .fg(theme::ACCENT)
            .add_modifier(Modifier::BOLD);
        let desc_style = Style::default().fg(theme::TEXT);
        let section_style = Style::default()
            .fg(Color::Rgb(250, 179, 135))
            .add_modifier(Modifier::BOLD);
        let dim = Style::default().fg(theme::TEXT_DIM);

        let lines = vec![
            Line::from(""),
            Line::from(Span::styled(
                format!("  {}", t("Navigation panel", lang)),
                section_style,
            )),
            help_line("  j / ↓       ", t("Move selection down", lang), key_style, desc_style),
            help_line("  k / ↑       ", t("Move selection up", lang), key_style, desc_style),
            help_line("  Enter       ", t("Expand group / Open document", lang), key_style, desc_style),
            help_line("  Esc         ", t("Collapse group / Clear search", lang), key_style, desc_style),
            help_line("  1-8         ", t("Jump to group by number", lang), key_style, desc_style),
            Line::from(""),
            Line::from(Span::styled(
                format!("  {}", t("Metadata panel", lang)),
                section_style,
            )),
            help_line("  j / ↓       ", t("Move between related links", lang), key_style, desc_style),
            help_line("  k / ↑       ", t("Move between related links", lang), key_style, desc_style),
            help_line("  Enter       ", t("Follow selected related link", lang), key_style, desc_style),
            help_line("  Esc         ", t("Back to Navigation", lang), key_style, desc_style),
            Line::from(""),
            Line::from(Span::styled(
                format!("  {}", t("Document panel", lang)),
                section_style,
            )),
            help_line("  j / ↓       ", t("Scroll down", lang), key_style, desc_style),
            help_line("  k / ↑       ", t("Scroll up", lang), key_style, desc_style),
            help_line("  g / G       ", t("Top / Bottom of document", lang), key_style, desc_style),
            help_line("  Ctrl+d / u  ", t("Half page down / up", lang), key_style, desc_style),
            help_line("  PgDn / PgUp ", t("Page down / up", lang), key_style, desc_style),
            help_line("  n / N       ", t("Next / Previous document", lang), key_style, desc_style),
            help_line("  f           ", t("Toggle fullscreen", lang), key_style, desc_style),
            help_line("  Esc         ", t("Exit fullscreen / Back to Nav", lang), key_style, desc_style),
            Line::from(""),
            Line::from(Span::styled(
                format!("  {}", t("General", lang)),
                section_style,
            )),
            help_line("  Tab         ", t("Next panel: Nav → Meta → Doc", lang), key_style, desc_style),
            help_line("  Shift+Tab   ", t("Prev panel: Doc → Meta → Nav", lang), key_style, desc_style),
            help_line("  /           ", t("Search by name, title, tags, date", lang), key_style, desc_style),
            help_line("  s           ", t("Cycle sort order", lang), key_style, desc_style),
            help_line("  r           ", t("Refresh document index", lang), key_style, desc_style),
            help_line("  L           ", t("Cycle display language", lang), key_style, desc_style),
            help_line("  q           ", t("Quit", lang), key_style, desc_style),
            help_line("  Ctrl+C      ", t("Force quit", lang), key_style, desc_style),
            Line::from(""),
            Line::from(Span::styled(
                format!("  {}", t("Press any key to close", lang)),
                dim,
            )),
        ];

        let paragraph = Paragraph::new(lines).block(block);
        paragraph.render(popup, buf);
    }
}

fn help_line<'a>(key: &'a str, desc: &'a str, key_style: Style, desc_style: Style) -> Line<'a> {
    Line::from(vec![
        Span::styled(key, key_style),
        Span::styled(desc, desc_style),
    ])
}

fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
    let vertical = Layout::vertical([
        Constraint::Percentage((100 - percent_y) / 2),
        Constraint::Percentage(percent_y),
        Constraint::Percentage((100 - percent_y) / 2),
    ])
    .split(r);

    Layout::horizontal([
        Constraint::Percentage((100 - percent_x) / 2),
        Constraint::Percentage(percent_x),
        Constraint::Percentage((100 - percent_x) / 2),
    ])
    .split(vertical[1])[1]
}