use super::*;
fn help() -> Vec<(&'static str, Vec<String>)> {
let description = if env!("CARGO_PKG_DESCRIPTION") == "A synthwave dual-pane TUI file manager" {
t!("A synthwave dual-pane TUI file manager")
} else {
env!("CARGO_PKG_DESCRIPTION")
};
vec![
(
t!("Keys"),
vec![
t!("F1 - This help").to_string(),
t!("F2 - Rename folder/file").to_string(),
format!(" {}", t!("with a selection: all by [N] [E] [C]")),
format!(" {}", t!("Shift+F2 compare directories")),
t!("F3 - View file (X text/hex/image)").to_string(),
format!(" {}", t!("F fit/fill, +/- zoom, W wrap")),
format!(" {}", t!("Shift+F3 (or =) compare two files")),
t!("F4 - Edit file (Ctrl+S/F2 save)").to_string(),
format!(" {}", t!("Ctrl+F find, F3/Shift+F3 next/prev")),
format!(" {}", t!("Ctrl+G go to line, Ctrl+R/F4 replace")),
format!(" {}", t!("Ctrl+Z undo, Ctrl+Y redo, Ctrl+X/C/V")),
format!(" {}", t!("Shift+arrows select, Ctrl+A all")),
format!(" {}", t!("Tab/Shift+Tab indent, Ctrl+/ comment")),
format!(" {}", t!("Alt+R in Find or Replace: regex")),
t!("F5 - Copy (to the other panel, or where typed)").to_string(),
format!(" {}", t!("Alt+F5 (or Ctrl+Shift+F5) link, Tab hard")),
t!("F6 - Move (to the other panel, or where typed)").to_string(),
t!("F7 - Create directory").to_string(),
format!(" {}", t!("Alt+F7 (or Ctrl+F7) find files")),
format!(" {}", t!("by name, text, regex, size, date")),
format!(" {}", t!("F on the results feeds them to the panel")),
t!("Shift+F4 - Create file").to_string(),
t!("F8 - Move to trash").to_string(),
format!(" {}", t!("Shift+F8 delete permanently")),
t!("Alt+A - Permissions, owner, time").to_string(),
t!("Alt+P - Pack into an archive (Tab format)").to_string(),
t!("Alt+H - Checksums (Tab algorithm); check a file").to_string(),
t!("Alt+S - Synchronize the two directories").to_string(),
t!("F9 - Open terminal").to_string(),
t!("Alt+M - User menu (E edits it)").to_string(),
t!("F10 - Quit").to_string(),
format!(" {}", t!("Shift+F10 quit, cd84 staying put")),
t!("F11 - Options (Alt+F1/F2 drives)").to_string(),
t!("F12 - Preview in other panel").to_string(),
t!("Enter - Open dir, or go into an archive").to_string(),
format!(" {}", t!(".zip .7z .tar(.gz .zst .xz .bz2): read-only")),
format!(" {}", t!("F5 copies out, Alt+F9 (or Ctrl+F9) all of it")),
format!(" {}", t!("Shift+Enter (or Ctrl+O) open as the system does")),
format!(" {}", t!("Alt+O open with... (E per extension)")),
t!("Ctrl+Left/Right - Dir to that panel").to_string(),
format!(" {}", t!("Ctrl+U swap panels, Alt+I same dir there")),
t!("Ctrl+L - Go to path (or click it)").to_string(),
format!(" {}", t!("Tab completes a name")),
format!(" {}", t!("Alt+Left/Right back/forward")),
format!(" {}", t!("Alt+Down every directory visited")),
format!(" {}", t!("Click the git branch to switch it")),
t!("Ctrl+D - Bookmarks").to_string(),
t!("Ctrl+T/Ctrl+W - New/close tab").to_string(),
format!(" {}", t!("Ctrl+PgUp/PgDn, Alt+1..9 switch")),
t!("Space - Select/deselect file").to_string(),
format!(" {}", t!("Alt+Shift+Enter size every directory")),
t!("+/- select/deselect by pattern, * invert").to_string(),
format!(" {}", t!("Alt+* invert with directories")),
t!("Ctrl+S - Filter the panel by pattern").to_string(),
t!("Ctrl+B - Every file below, flat").to_string(),
t!("Ctrl+C/Alt+C - Copy paths/names").to_string(),
t!("Ctrl+F3..F6 - Sort name/ext/date/size").to_string(),
t!("Ctrl+R - Reload both panels").to_string(),
t!("Type to search, Esc to clear").to_string(),
],
),
(
t!("About"),
vec![
concat!("FM84 - File Manager '84, v", env!("CARGO_PKG_VERSION")).to_string(),
description.to_string(),
t!("Built with Rust, Ratatui and Crossterm").to_string(),
"Copyright (c) 2025-2026 Colt Willcox".to_string(),
t!("Released under the {license} License", license = env!("CARGO_PKG_LICENSE")),
env!("CARGO_PKG_REPOSITORY").to_string(),
],
),
]
}
pub fn help_lines() -> Vec<(String, bool)> {
let mut lines = Vec::new();
for (heading, items) in help() {
if !lines.is_empty() {
lines.push((String::new(), false));
}
lines.push((format!(" {heading}"), true));
lines.extend(items.iter().map(|item| (format!(" {item}"), false)));
}
lines
}
pub(super) fn render_help_popup(f: &mut ratatui::Frame<'_>, area: Rect, scroll: usize) -> usize {
let popup_width = area.width as usize * POPUP_WIDTH as usize / 100;
let help = help_lines();
let popup_area = centered(screen_layout(area)[2], popup_width.max(1), help.len() + 4);
let inner = popup_area.inner(Margin { vertical: 2, horizontal: 2 });
let room = (inner.height as usize).max(1);
let first = scroll.min(help.len().saturating_sub(room));
let mut popup_block = Block::default().title(Line::from(Span::styled(format!(" {} ", t!("Help/About")), style_title())).centered()).borders(Borders::ALL).style(style_border());
if room < help.len() {
let shown = format!(" {}-{}/{} ", first + 1, (first + room).min(help.len()), help.len());
popup_block = popup_block.title_bottom(Line::from(Span::styled(shown, style_columns())).right_aligned());
}
clear(f, popup_area);
f.render_widget(popup_block, popup_area);
let max_len = help.iter().map(|(text, _)| text.chars().count()).max().unwrap_or(0);
let lines: Vec<Line> = help
.iter()
.skip(first)
.take(room)
.map(|(text, heading)| {
let style = if *heading { style_title().add_modifier(Modifier::BOLD) } else { style_title() };
Line::from(Span::styled(format!("{text:<max_len$}"), style))
})
.collect();
f.render_widget(Paragraph::new(lines).alignment(Alignment::Center), inner);
room
}