From 0f8493eb02d179536a60d72bbc88d64b3c360304 Mon Sep 17 00:00:00 2001 From: Tyler Hallada Date: Fri, 7 Aug 2026 01:13:17 +0000 Subject: [PATCH] style: tidy the thin-binary split Follow-up cleanup to the previous commit: - rustfmt src/run.rs. The mechanical crate:: qualification pushed lines past 100 chars and left 'use crate::i18n::t' out of sort order; the original main.rs had been rustfmt-clean. Verified whitespace-only: the token stream is identical modulo trailing commas and that one import moving to its sorted position. - Drop #![allow(dead_code)] from lib.rs. It existed because the library was a benches-only shim exposing modules nothing called. Now that the lib IS the application, it masks nothing -- cargo check is warning-free without it, so removing it restores real dead-code detection. - Collapse the duplicated explanatory comments. The rationale lived in both lib.rs and main.rs; state it once as a lib.rs doc comment and leave main.rs as the three lines it should be. Full history is in docs/BUILDING.md. --- src/lib.rs | 14 +- src/main.rs | 7 - src/run.rs | 766 +++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 559 insertions(+), 228 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index de4233f..9322cb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,8 @@ -// The keydr library. This holds ALL application code, including the TUI -// event loop and rendering (`run`), so that everything is compiled exactly -// once. src/main.rs is a thin wrapper that just calls `run()`. -// -// Previously main.rs re-declared this same module tree, making the binary a -// second independent crate: every module -- and the whole rust-i18n -// translation table generated by `i18n!` -- was compiled twice. -#![allow(dead_code)] +//! keydr — terminal typing tutor with adaptive learning. +//! +//! All application code lives here, including the TUI event loop (`run`). +//! `src/main.rs` is a thin wrapper so nothing is compiled twice; see +//! docs/BUILDING.md. New modules belong in this file, not in main.rs. rust_i18n::i18n!("locales", fallback = "en"); @@ -17,7 +14,6 @@ pub mod l10n; pub mod session; pub mod store; -// Internal to the application, but `run` needs them. mod app; mod event; mod generator; diff --git a/src/main.rs b/src/main.rs index 80d98e6..41ba9c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,3 @@ -// Thin entry point. -// -// All application code lives in the `keydr` library (see src/run.rs), so it -// is compiled exactly once. Previously this file re-declared the whole -// module tree (`mod app; mod config; ...`), which made the binary a second, -// separate crate — every module, and the entire rust-i18n translation -// table, was compiled twice. fn main() -> anyhow::Result<()> { keydr::run() } diff --git a/src/run.rs b/src/run.rs index bc86b38..33b63cd 100644 --- a/src/run.rs +++ b/src/run.rs @@ -20,11 +20,11 @@ use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Padding, Paragraph, Widget, Wrap}; use crate::app::{App, AppScreen, DrillMode, MilestoneKind, SettingItem, StatusKind}; -use crate::i18n::t; use crate::engine::skill_tree::{BranchStatus, DrillScope, find_key_branch, get_branch_definition}; use crate::event::{AppEvent, EventHandler}; use crate::generator::code_syntax::{code_language_options, is_language_cached, language_by_key}; use crate::generator::passage::{is_book_cached, passage_options}; +use crate::i18n::t; use crate::keyboard::display::key_display_name; use crate::keyboard::finger::Hand; use crate::l10n::language_pack::{ @@ -510,15 +510,13 @@ fn milestone_footer_hint_token_at( let inner = Block::bordered().inner(overlay_area); let footer_y = inner.y + inner.height.saturating_sub(1); let footer_area = Rect::new(inner.x, footer_y, inner.width, 1); - let hint_skill_tree = crate::ui::hint::hint(crate::ui::hint::K_T, t!("milestones.hint_skill_tree_continue").as_ref()); + let hint_skill_tree = crate::ui::hint::hint( + crate::ui::hint::K_T, + t!("milestones.hint_skill_tree_continue").as_ref(), + ); let hint_any_key = t!("milestones.hint_any_key"); let hints: Vec<&str> = vec![hint_skill_tree.as_str(), hint_any_key.as_ref()]; - hint_token_at( - footer_area, - &hints, - x, - y, - ) + hint_token_at(footer_area, &hints, x, y) } fn handle_mouse(app: &mut App, mouse: MouseEvent) { @@ -606,12 +604,18 @@ fn handle_menu_mouse(app: &mut App, mouse: MouseEvent) { MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Down(MouseButton::Right) => { let is_secondary = matches!(mouse.kind, MouseEventKind::Down(MouseButton::Right)); let area = terminal_area(); - let mh_start = crate::ui::hint::hint(crate::ui::hint::K_1_3, t!("menu.hint_start").as_ref()); - let mh_tree = crate::ui::hint::hint(crate::ui::hint::K_T, t!("menu.hint_skill_tree").as_ref()); - let mh_kbd = crate::ui::hint::hint(crate::ui::hint::K_B, t!("menu.hint_keyboard").as_ref()); - let mh_stats = crate::ui::hint::hint(crate::ui::hint::K_S, t!("menu.hint_stats").as_ref()); - let mh_settings = crate::ui::hint::hint(crate::ui::hint::K_C, t!("menu.hint_settings").as_ref()); - let mh_quit = crate::ui::hint::hint(crate::ui::hint::K_Q, t!("menu.hint_quit").as_ref()); + let mh_start = + crate::ui::hint::hint(crate::ui::hint::K_1_3, t!("menu.hint_start").as_ref()); + let mh_tree = + crate::ui::hint::hint(crate::ui::hint::K_T, t!("menu.hint_skill_tree").as_ref()); + let mh_kbd = + crate::ui::hint::hint(crate::ui::hint::K_B, t!("menu.hint_keyboard").as_ref()); + let mh_stats = + crate::ui::hint::hint(crate::ui::hint::K_S, t!("menu.hint_stats").as_ref()); + let mh_settings = + crate::ui::hint::hint(crate::ui::hint::K_C, t!("menu.hint_settings").as_ref()); + let mh_quit = + crate::ui::hint::hint(crate::ui::hint::K_Q, t!("menu.hint_quit").as_ref()); let menu_hints: Vec<&str> = vec![ mh_start.as_str(), mh_tree.as_str(), @@ -697,7 +701,10 @@ fn handle_drill_mouse(app: &mut App, mouse: MouseEvent) { let layout = AppLayout::new(terminal_area()); if point_in_rect(mouse.column, mouse.row, layout.footer) { let hint_end = crate::ui::hint::hint(crate::ui::hint::K_ESC, t!("drill.hint_end").as_ref()); - let hint_bs = crate::ui::hint::hint(crate::ui::hint::K_BACKSPACE, t!("drill.hint_backspace").as_ref()); + let hint_bs = crate::ui::hint::hint( + crate::ui::hint::K_BACKSPACE, + t!("drill.hint_backspace").as_ref(), + ); let hints: Vec<&str> = vec![hint_end.as_str(), hint_bs.as_str()]; if let Some(token) = hint_token_at(layout.footer, &hints, mouse.column, mouse.row) { match token.as_str() { @@ -758,11 +765,18 @@ fn handle_result_mouse(app: &mut App, mouse: MouseEvent) { let area = terminal_area(); let centered = crate::ui::layout::centered_rect(60, 70, area); let inner = Block::bordered().inner(centered); - let h_cont = crate::ui::hint::hint(crate::ui::hint::K_C_ENTER_SPACE, t!("dashboard.hint_continue").as_ref()); - let h_retry = crate::ui::hint::hint(crate::ui::hint::K_R, t!("dashboard.hint_retry").as_ref()); - let h_menu = crate::ui::hint::hint(crate::ui::hint::K_Q, t!("dashboard.hint_menu").as_ref()); - let h_stats = crate::ui::hint::hint(crate::ui::hint::K_S, t!("dashboard.hint_stats").as_ref()); - let h_del = crate::ui::hint::hint(crate::ui::hint::K_X, t!("dashboard.hint_delete").as_ref()); + let h_cont = crate::ui::hint::hint( + crate::ui::hint::K_C_ENTER_SPACE, + t!("dashboard.hint_continue").as_ref(), + ); + let h_retry = + crate::ui::hint::hint(crate::ui::hint::K_R, t!("dashboard.hint_retry").as_ref()); + let h_menu = + crate::ui::hint::hint(crate::ui::hint::K_Q, t!("dashboard.hint_menu").as_ref()); + let h_stats = + crate::ui::hint::hint(crate::ui::hint::K_S, t!("dashboard.hint_stats").as_ref()); + let h_del = + crate::ui::hint::hint(crate::ui::hint::K_X, t!("dashboard.hint_delete").as_ref()); let hints: Vec<&str> = vec![ h_cont.as_str(), h_retry.as_str(), @@ -873,9 +887,11 @@ fn handle_stats_mouse(app: &mut App, mouse: MouseEvent) { let tab_line_count = wrapped_stats_tab_line_count(width) as u16; let sh_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("stats.hint_back").as_ref()); let sh_next = crate::ui::hint::hint(crate::ui::hint::K_TAB, t!("stats.hint_next_tab").as_ref()); - let sh_switch = crate::ui::hint::hint(crate::ui::hint::K_1_6, t!("stats.hint_switch_tab").as_ref()); + let sh_switch = + crate::ui::hint::hint(crate::ui::hint::K_1_6, t!("stats.hint_switch_tab").as_ref()); let sh_nav = crate::ui::hint::hint(crate::ui::hint::K_J_K, t!("stats.hint_navigate").as_ref()); - let sh_page = crate::ui::hint::hint(crate::ui::hint::K_PGUP_PGDN, t!("stats.hint_page").as_ref()); + let sh_page = + crate::ui::hint::hint(crate::ui::hint::K_PGUP_PGDN, t!("stats.hint_page").as_ref()); let sh_del = crate::ui::hint::hint(crate::ui::hint::K_X, t!("stats.hint_delete").as_ref()); let footer_hints: Vec<&str> = if app.stats_tab == 1 { vec![ @@ -1200,14 +1216,19 @@ fn handle_settings_mouse(app: &mut App, mouse: MouseEvent) { let inner = Block::bordered().inner(centered); let fields = settings_fields(app); let header_height = if inner.height > 0 { 1 } else { 0 }; - let sfh_save = crate::ui::hint::hint(crate::ui::hint::K_ESC, t!("settings.hint_save_back").as_ref()); - let sfh_change = crate::ui::hint::hint(crate::ui::hint::K_ENTER_ARROWS, t!("settings.hint_change_value").as_ref()); - let sfh_edit = crate::ui::hint::hint(crate::ui::hint::K_ENTER_ON_PATH, t!("settings.hint_edit_path").as_ref()); - let footer_hints: Vec<&str> = vec![ - sfh_save.as_str(), - sfh_change.as_str(), - sfh_edit.as_str(), - ]; + let sfh_save = crate::ui::hint::hint( + crate::ui::hint::K_ESC, + t!("settings.hint_save_back").as_ref(), + ); + let sfh_change = crate::ui::hint::hint( + crate::ui::hint::K_ENTER_ARROWS, + t!("settings.hint_change_value").as_ref(), + ); + let sfh_edit = crate::ui::hint::hint( + crate::ui::hint::K_ENTER_ON_PATH, + t!("settings.hint_edit_path").as_ref(), + ); + let footer_hints: Vec<&str> = vec![sfh_save.as_str(), sfh_change.as_str(), sfh_edit.as_str()]; let footer_height = if inner.height > header_height { pack_hint_lines(&footer_hints, inner.width as usize) .len() @@ -1226,10 +1247,20 @@ fn handle_settings_mouse(app: &mut App, mouse: MouseEvent) { .split(inner); if is_click && layout[2].height > 0 { - let efh_move = crate::ui::hint::hint(crate::ui::hint::K_ARROW_LR, t!("settings.hint_move").as_ref()); - let efh_tab = crate::ui::hint::hint(crate::ui::hint::K_TAB, t!("settings.hint_tab_complete").as_ref()); - let efh_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("settings.hint_confirm").as_ref()); - let efh_cancel = crate::ui::hint::hint(crate::ui::hint::K_ESC, t!("settings.hint_cancel").as_ref()); + let efh_move = crate::ui::hint::hint( + crate::ui::hint::K_ARROW_LR, + t!("settings.hint_move").as_ref(), + ); + let efh_tab = crate::ui::hint::hint( + crate::ui::hint::K_TAB, + t!("settings.hint_tab_complete").as_ref(), + ); + let efh_confirm = crate::ui::hint::hint( + crate::ui::hint::K_ENTER, + t!("settings.hint_confirm").as_ref(), + ); + let efh_cancel = + crate::ui::hint::hint(crate::ui::hint::K_ESC, t!("settings.hint_cancel").as_ref()); let footer_hints: Vec<&str> = if app.settings_editing_path.is_some() { vec![ efh_move.as_str(), @@ -1238,11 +1269,7 @@ fn handle_settings_mouse(app: &mut App, mouse: MouseEvent) { efh_cancel.as_str(), ] } else { - vec![ - sfh_save.as_str(), - sfh_change.as_str(), - sfh_edit.as_str(), - ] + vec![sfh_save.as_str(), sfh_change.as_str(), sfh_edit.as_str()] }; if let Some(token) = hint_token_at(layout[2], &footer_hints, mouse.column, mouse.row) { if app.settings_editing_path.is_some() { @@ -1676,8 +1703,12 @@ fn is_dictionary_language_disabled(_app: &App, language_key: &str) -> bool { fn dictionary_language_list_area(area: Rect) -> Rect { let centered = crate::ui::layout::centered_rect(60, 70, area); let inner = Block::bordered().inner(centered); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let hint_lines = pack_hint_lines( &[h_nav.as_str(), h_confirm.as_str(), h_back.as_str()], @@ -1793,9 +1824,14 @@ fn handle_dictionary_language_mouse(app: &mut App, mouse: MouseEvent) { let area = terminal_area(); let centered = crate::ui::layout::centered_rect(60, 70, area); let inner = Block::bordered().inner(centered); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); - let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_back = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let hints: Vec<&str> = vec![h_nav.as_str(), h_confirm.as_str(), h_back.as_str()]; let footer_h = pack_hint_lines(&hints, inner.width as usize).len().max(1) as u16; let chunks = if inner.height > footer_h { @@ -1918,8 +1954,12 @@ fn is_keyboard_layout_disabled(layout_key: &str) -> bool { fn keyboard_layout_list_area(area: Rect) -> Rect { let centered = crate::ui::layout::centered_rect(60, 70, area); let inner = Block::bordered().inner(centered); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let hint_lines = pack_hint_lines( &[h_nav.as_str(), h_confirm.as_str(), h_back.as_str()], @@ -2022,9 +2062,14 @@ fn handle_keyboard_layout_mouse(app: &mut App, mouse: MouseEvent) { let area = terminal_area(); let centered = crate::ui::layout::centered_rect(60, 70, area); let inner = Block::bordered().inner(centered); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); - let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_back = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let hints: Vec<&str> = vec![h_nav.as_str(), h_confirm.as_str(), h_back.as_str()]; let footer_h = pack_hint_lines(&hints, inner.width as usize).len().max(1) as u16; let chunks = if inner.height > footer_h { @@ -2137,8 +2182,12 @@ fn code_language_list_area(app: &App, area: Rect) -> Rect { let inner = Block::bordered().inner(centered); let options = code_language_options(); let width = inner.width as usize; - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let hint_lines = pack_hint_lines( &[h_nav.as_str(), h_confirm.as_str(), h_back.as_str()], @@ -2184,9 +2233,14 @@ fn handle_code_language_mouse(app: &mut App, mouse: MouseEvent) { MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Down(MouseButton::Right) => { let centered = crate::ui::layout::centered_rect(50, 70, terminal_area()); let inner = Block::bordered().inner(centered); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); - let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_back = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let hints: Vec<&str> = vec![h_nav.as_str(), h_confirm.as_str(), h_back.as_str()]; let hint_lines = pack_hint_lines(&hints, inner.width as usize); let disabled_notice_t = t!("select.disabled_network_notice"); @@ -2329,8 +2383,12 @@ fn passage_book_list_area(app: &App, area: Rect) -> Rect { let inner = Block::bordered().inner(centered); let options = passage_options(); let width = inner.width as usize; - let h_nav_t = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("select.hint_navigate").as_ref()); - let h_confirm_t = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_nav_t = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm_t = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); let h_back_t = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let hint_lines = pack_hint_lines( &[h_nav_t.as_str(), h_confirm_t.as_str(), h_back_t.as_str()], @@ -2375,9 +2433,14 @@ fn handle_passage_book_mouse(app: &mut App, mouse: MouseEvent) { MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Down(MouseButton::Right) => { let centered = crate::ui::layout::centered_rect(60, 70, terminal_area()); let inner = Block::bordered().inner(centered); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); - let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_back = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let hints: Vec<&str> = vec![h_nav.as_str(), h_confirm.as_str(), h_back.as_str()]; let hint_lines = pack_hint_lines(&hints, inner.width as usize); let disabled_notice_t = t!("select.disabled_sources_notice"); @@ -2562,13 +2625,30 @@ fn intro_field_at_row(base_y: u16, y: u16) -> Option<(usize, bool)> { fn passage_intro_content_area(area: Rect) -> Rect { let centered = crate::ui::layout::centered_rect(75, 80, area); let inner = Block::bordered().inner(centered); - let ih_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("intro.hint_navigate").as_ref()); - let ih_adj = crate::ui::hint::hint(crate::ui::hint::K_LEFT_RIGHT, t!("intro.hint_adjust").as_ref()); - let ih_edit = crate::ui::hint::hint(crate::ui::hint::K_TYPE_BACKSPACE, t!("intro.hint_edit").as_ref()); - let ih_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); - let ih_cancel = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); + let ih_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("intro.hint_navigate").as_ref(), + ); + let ih_adj = crate::ui::hint::hint( + crate::ui::hint::K_LEFT_RIGHT, + t!("intro.hint_adjust").as_ref(), + ); + let ih_edit = crate::ui::hint::hint( + crate::ui::hint::K_TYPE_BACKSPACE, + t!("intro.hint_edit").as_ref(), + ); + let ih_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); + let ih_cancel = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); let hint_lines = pack_hint_lines( - &[ih_nav.as_str(), ih_adj.as_str(), ih_edit.as_str(), ih_confirm.as_str(), ih_cancel.as_str()], + &[ + ih_nav.as_str(), + ih_adj.as_str(), + ih_edit.as_str(), + ih_confirm.as_str(), + ih_cancel.as_str(), + ], inner.width as usize, ); let footer_height = (hint_lines.len() + 1) as u16; @@ -2597,12 +2677,29 @@ fn handle_passage_intro_mouse(app: &mut App, mouse: MouseEvent) { MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Down(MouseButton::Right) => { let centered = crate::ui::layout::centered_rect(75, 80, terminal_area()); let inner = Block::bordered().inner(centered); - let ih_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("intro.hint_navigate").as_ref()); - let ih_adj = crate::ui::hint::hint(crate::ui::hint::K_LEFT_RIGHT, t!("intro.hint_adjust").as_ref()); - let ih_edit = crate::ui::hint::hint(crate::ui::hint::K_TYPE_BACKSPACE, t!("intro.hint_edit").as_ref()); - let ih_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); - let ih_cancel = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); - let hints: Vec<&str> = vec![ih_nav.as_str(), ih_adj.as_str(), ih_edit.as_str(), ih_confirm.as_str(), ih_cancel.as_str()]; + let ih_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("intro.hint_navigate").as_ref(), + ); + let ih_adj = crate::ui::hint::hint( + crate::ui::hint::K_LEFT_RIGHT, + t!("intro.hint_adjust").as_ref(), + ); + let ih_edit = crate::ui::hint::hint( + crate::ui::hint::K_TYPE_BACKSPACE, + t!("intro.hint_edit").as_ref(), + ); + let ih_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); + let ih_cancel = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); + let hints: Vec<&str> = vec![ + ih_nav.as_str(), + ih_adj.as_str(), + ih_edit.as_str(), + ih_confirm.as_str(), + ih_cancel.as_str(), + ]; let hint_lines = pack_hint_lines(&hints, inner.width as usize); let footer_height = (hint_lines.len() + 1) as u16; if footer_height > 0 && footer_height < inner.height { @@ -2777,13 +2874,30 @@ fn handle_code_intro_key(app: &mut App, key: KeyEvent) { fn code_intro_content_area(area: Rect) -> Rect { let centered = crate::ui::layout::centered_rect(75, 80, area); let inner = Block::bordered().inner(centered); - let ih_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("intro.hint_navigate").as_ref()); - let ih_adj = crate::ui::hint::hint(crate::ui::hint::K_LEFT_RIGHT, t!("intro.hint_adjust").as_ref()); - let ih_edit = crate::ui::hint::hint(crate::ui::hint::K_TYPE_BACKSPACE, t!("intro.hint_edit").as_ref()); - let ih_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); - let ih_cancel = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); + let ih_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("intro.hint_navigate").as_ref(), + ); + let ih_adj = crate::ui::hint::hint( + crate::ui::hint::K_LEFT_RIGHT, + t!("intro.hint_adjust").as_ref(), + ); + let ih_edit = crate::ui::hint::hint( + crate::ui::hint::K_TYPE_BACKSPACE, + t!("intro.hint_edit").as_ref(), + ); + let ih_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); + let ih_cancel = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); let hint_lines = pack_hint_lines( - &[ih_nav.as_str(), ih_adj.as_str(), ih_edit.as_str(), ih_confirm.as_str(), ih_cancel.as_str()], + &[ + ih_nav.as_str(), + ih_adj.as_str(), + ih_edit.as_str(), + ih_confirm.as_str(), + ih_cancel.as_str(), + ], inner.width as usize, ); let footer_height = (hint_lines.len() + 1) as u16; @@ -2812,12 +2926,29 @@ fn handle_code_intro_mouse(app: &mut App, mouse: MouseEvent) { MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Down(MouseButton::Right) => { let centered = crate::ui::layout::centered_rect(75, 80, terminal_area()); let inner = Block::bordered().inner(centered); - let ih_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("intro.hint_navigate").as_ref()); - let ih_adj = crate::ui::hint::hint(crate::ui::hint::K_LEFT_RIGHT, t!("intro.hint_adjust").as_ref()); - let ih_edit = crate::ui::hint::hint(crate::ui::hint::K_TYPE_BACKSPACE, t!("intro.hint_edit").as_ref()); - let ih_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); - let ih_cancel = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); - let hints: Vec<&str> = vec![ih_nav.as_str(), ih_adj.as_str(), ih_edit.as_str(), ih_confirm.as_str(), ih_cancel.as_str()]; + let ih_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("intro.hint_navigate").as_ref(), + ); + let ih_adj = crate::ui::hint::hint( + crate::ui::hint::K_LEFT_RIGHT, + t!("intro.hint_adjust").as_ref(), + ); + let ih_edit = crate::ui::hint::hint( + crate::ui::hint::K_TYPE_BACKSPACE, + t!("intro.hint_edit").as_ref(), + ); + let ih_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); + let ih_cancel = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); + let hints: Vec<&str> = vec![ + ih_nav.as_str(), + ih_adj.as_str(), + ih_edit.as_str(), + ih_confirm.as_str(), + ih_cancel.as_str(), + ]; let hint_lines = pack_hint_lines(&hints, inner.width as usize); let footer_height = (hint_lines.len() + 1) as u16; if footer_height > 0 && footer_height < inner.height { @@ -2984,7 +3115,11 @@ struct SkillTreeMouseLayout { } fn locked_branch_notice(app: &App) -> String { - t!("skill_tree.locked_notice", count = app.skill_tree.primary_letters().len()).to_string() + t!( + "skill_tree.locked_notice", + count = app.skill_tree.primary_letters().len() + ) + .to_string() } fn skill_tree_interactive_areas(app: &App, area: Rect) -> SkillTreeMouseLayout { @@ -2997,19 +3132,30 @@ fn skill_tree_interactive_areas(app: &App, area: Rect) -> SkillTreeMouseLayout { let bp = branches .get(selected) .map(|id| app.skill_tree.branch_progress(*id)); - let st_nav = crate::ui::hint::hint(crate::ui::hint::K_UD_JK, t!("skill_tree.hint_navigate").as_ref()); - let st_scroll = crate::ui::hint::hint(crate::ui::hint::K_SCROLL_KEYS, t!("skill_tree.hint_scroll").as_ref()); - let st_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("skill_tree.hint_back").as_ref()); - let st_unlock = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("skill_tree.hint_unlock").as_ref()); - let st_drill = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("skill_tree.hint_start_drill").as_ref()); + let st_nav = crate::ui::hint::hint( + crate::ui::hint::K_UD_JK, + t!("skill_tree.hint_navigate").as_ref(), + ); + let st_scroll = crate::ui::hint::hint( + crate::ui::hint::K_SCROLL_KEYS, + t!("skill_tree.hint_scroll").as_ref(), + ); + let st_back = crate::ui::hint::hint( + crate::ui::hint::K_Q_ESC, + t!("skill_tree.hint_back").as_ref(), + ); + let st_unlock = crate::ui::hint::hint( + crate::ui::hint::K_ENTER, + t!("skill_tree.hint_unlock").as_ref(), + ); + let st_drill = crate::ui::hint::hint( + crate::ui::hint::K_ENTER, + t!("skill_tree.hint_start_drill").as_ref(), + ); let (footer_hints, footer_notice): (Vec<&str>, Option) = match bp.map(|b| b.status.clone()) { Some(BranchStatus::Locked) => ( - vec![ - st_nav.as_str(), - st_scroll.as_str(), - st_back.as_str(), - ], + vec![st_nav.as_str(), st_scroll.as_str(), st_back.as_str()], Some(locked_branch_notice(app)), ), Some(BranchStatus::Available) => ( @@ -3031,11 +3177,7 @@ fn skill_tree_interactive_areas(app: &App, area: Rect) -> SkillTreeMouseLayout { None, ), _ => ( - vec![ - st_nav.as_str(), - st_scroll.as_str(), - st_back.as_str(), - ], + vec![st_nav.as_str(), st_scroll.as_str(), st_back.as_str()], None, ), }; @@ -3188,21 +3330,32 @@ fn handle_skill_tree_mouse(app: &mut App, mouse: MouseEvent) { .skill_tree_selected .min(branches.len().saturating_sub(1)); let bp = app.skill_tree.branch_progress(branches[selected]); - let st_nav = crate::ui::hint::hint(crate::ui::hint::K_UD_JK, t!("skill_tree.hint_navigate").as_ref()); - let st_scroll = crate::ui::hint::hint(crate::ui::hint::K_SCROLL_KEYS, t!("skill_tree.hint_scroll").as_ref()); - let st_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("skill_tree.hint_back").as_ref()); - let st_unlock = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("skill_tree.hint_unlock").as_ref()); - let st_drill = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("skill_tree.hint_start_drill").as_ref()); + let st_nav = crate::ui::hint::hint( + crate::ui::hint::K_UD_JK, + t!("skill_tree.hint_navigate").as_ref(), + ); + let st_scroll = crate::ui::hint::hint( + crate::ui::hint::K_SCROLL_KEYS, + t!("skill_tree.hint_scroll").as_ref(), + ); + let st_back = crate::ui::hint::hint( + crate::ui::hint::K_Q_ESC, + t!("skill_tree.hint_back").as_ref(), + ); + let st_unlock = crate::ui::hint::hint( + crate::ui::hint::K_ENTER, + t!("skill_tree.hint_unlock").as_ref(), + ); + let st_drill = crate::ui::hint::hint( + crate::ui::hint::K_ENTER, + t!("skill_tree.hint_start_drill").as_ref(), + ); let (footer_hints, footer_notice): (Vec<&str>, Option) = if *app.skill_tree.branch_status(branches[selected]) == crate::engine::skill_tree::BranchStatus::Locked { ( - vec![ - st_nav.as_str(), - st_scroll.as_str(), - st_back.as_str(), - ], + vec![st_nav.as_str(), st_scroll.as_str(), st_back.as_str()], Some(locked_branch_notice(app)), ) } else if bp.status == crate::engine::skill_tree::BranchStatus::Available { @@ -3227,11 +3380,7 @@ fn handle_skill_tree_mouse(app: &mut App, mouse: MouseEvent) { ) } else { ( - vec![ - st_nav.as_str(), - st_scroll.as_str(), - st_back.as_str(), - ], + vec![st_nav.as_str(), st_scroll.as_str(), st_back.as_str()], None, ) }; @@ -3252,7 +3401,8 @@ fn handle_skill_tree_mouse(app: &mut App, mouse: MouseEvent) { inner.width, footer_height, ); - if let Some(token) = hint_token_at(footer_area, &footer_hints, mouse.column, mouse.row) { + if let Some(token) = hint_token_at(footer_area, &footer_hints, mouse.column, mouse.row) + { match token.as_str() { "q/ESC" => app.go_to_menu(), "Enter" => { @@ -3340,21 +3490,32 @@ fn skill_tree_detail_max_scroll(app: &App) -> usize { .skill_tree_selected .min(branches.len().saturating_sub(1)); let bp = app.skill_tree.branch_progress(branches[selected]); - let st_nav = crate::ui::hint::hint(crate::ui::hint::K_UD_JK, t!("skill_tree.hint_navigate").as_ref()); - let st_scroll = crate::ui::hint::hint(crate::ui::hint::K_SCROLL_KEYS, t!("skill_tree.hint_scroll").as_ref()); - let st_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("skill_tree.hint_back").as_ref()); - let st_unlock = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("skill_tree.hint_unlock").as_ref()); - let st_drill = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("skill_tree.hint_start_drill").as_ref()); + let st_nav = crate::ui::hint::hint( + crate::ui::hint::K_UD_JK, + t!("skill_tree.hint_navigate").as_ref(), + ); + let st_scroll = crate::ui::hint::hint( + crate::ui::hint::K_SCROLL_KEYS, + t!("skill_tree.hint_scroll").as_ref(), + ); + let st_back = crate::ui::hint::hint( + crate::ui::hint::K_Q_ESC, + t!("skill_tree.hint_back").as_ref(), + ); + let st_unlock = crate::ui::hint::hint( + crate::ui::hint::K_ENTER, + t!("skill_tree.hint_unlock").as_ref(), + ); + let st_drill = crate::ui::hint::hint( + crate::ui::hint::K_ENTER, + t!("skill_tree.hint_start_drill").as_ref(), + ); let (footer_hints, footer_notice): (Vec<&str>, Option) = if *app.skill_tree.branch_status(branches[selected]) == crate::engine::skill_tree::BranchStatus::Locked { ( - vec![ - st_nav.as_str(), - st_scroll.as_str(), - st_back.as_str(), - ], + vec![st_nav.as_str(), st_scroll.as_str(), st_back.as_str()], Some(locked_branch_notice(app)), ) } else if bp.status == crate::engine::skill_tree::BranchStatus::Available { @@ -3379,11 +3540,7 @@ fn skill_tree_detail_max_scroll(app: &App) -> usize { ) } else { ( - vec![ - st_nav.as_str(), - st_scroll.as_str(), - st_back.as_str(), - ], + vec![st_nav.as_str(), st_scroll.as_str(), st_back.as_str()], None, ) }; @@ -3484,7 +3641,8 @@ fn render_menu(frame: &mut ratatui::Frame, app: &App) { let mh_tree = crate::ui::hint::hint(crate::ui::hint::K_T, t!("menu.hint_skill_tree").as_ref()); let mh_kbd = crate::ui::hint::hint(crate::ui::hint::K_B, t!("menu.hint_keyboard").as_ref()); let mh_stats = crate::ui::hint::hint(crate::ui::hint::K_S, t!("menu.hint_stats").as_ref()); - let mh_settings = crate::ui::hint::hint(crate::ui::hint::K_C, t!("menu.hint_settings").as_ref()); + let mh_settings = + crate::ui::hint::hint(crate::ui::hint::K_C, t!("menu.hint_settings").as_ref()); let mh_quit = crate::ui::hint::hint(crate::ui::hint::K_Q, t!("menu.hint_quit").as_ref()); let menu_hints: Vec<&str> = vec![ mh_start.as_str(), @@ -3706,14 +3864,15 @@ fn render_drill(frame: &mut ratatui::Frame, app: &App) { let mut idx = 1; if progress_height > 0 { if app.drill_mode == DrillMode::Adaptive { - let progress_widget = crate::ui::components::branch_progress_list::BranchProgressList { - skill_tree: &app.skill_tree, - key_stats: &app.ranked_key_stats, - drill_scope: app.drill_scope, - active_branches: &active_branches, - theme: app.theme, - height: progress_height, - }; + let progress_widget = + crate::ui::components::branch_progress_list::BranchProgressList { + skill_tree: &app.skill_tree, + key_stats: &app.ranked_key_stats, + drill_scope: app.drill_scope, + active_branches: &active_branches, + theme: app.theme, + height: progress_height, + }; frame.render_widget(progress_widget, main_layout[idx]); } else { let source = app.drill_source_info.as_deref().unwrap_or("unknown source"); @@ -3816,9 +3975,18 @@ fn render_adaptive_intro_overlay(frame: &mut ratatui::Frame, app: &App) { let inner = block.inner(overlay_area); block.render(overlay_area, frame.buffer_mut()); - let hint_h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("adaptive_intro.hint_back").as_ref()); - let hint_h_adjust = crate::ui::hint::hint(crate::ui::hint::K_ARROW_LR, t!("adaptive_intro.hint_adjust").as_ref()); - let hint_h_start = crate::ui::hint::hint(crate::ui::hint::K_ENTER_SPACE, t!("adaptive_intro.hint_start").as_ref()); + let hint_h_back = crate::ui::hint::hint( + crate::ui::hint::K_Q_ESC, + t!("adaptive_intro.hint_back").as_ref(), + ); + let hint_h_adjust = crate::ui::hint::hint( + crate::ui::hint::K_ARROW_LR, + t!("adaptive_intro.hint_adjust").as_ref(), + ); + let hint_h_start = crate::ui::hint::hint( + crate::ui::hint::K_ENTER_SPACE, + t!("adaptive_intro.hint_start").as_ref(), + ); let hints: Vec<&str> = vec![ hint_h_adjust.as_str(), hint_h_start.as_str(), @@ -3859,7 +4027,9 @@ fn render_adaptive_intro_overlay(frame: &mut ratatui::Frame, app: &App) { lines.push(Line::from(vec![ Span::styled( format!("{wpm_label} "), - Style::default().fg(colors.fg()).add_modifier(Modifier::BOLD), + Style::default() + .fg(colors.fg()) + .add_modifier(Modifier::BOLD), ), Span::styled( wpm_value, @@ -3995,7 +4165,10 @@ fn render_milestone_overlay( name.to_string() } }; - let use_finger_msg = t!("milestones.use_finger", finger = finger_desc.to_lowercase().as_str()); + let use_finger_msg = t!( + "milestones.use_finger", + finger = finger_desc.to_lowercase().as_str() + ); lines.push(Line::from(Span::styled( format!(" {key_label}: {use_finger_msg}"), Style::default().fg(colors.fg()), @@ -4034,7 +4207,10 @@ fn render_milestone_overlay( MilestoneKind::BranchesAvailable => { lines.push(Line::from("")); let primary_count = app.skill_tree.primary_letters().len(); - let congrats_msg = t!("milestones.congratulations_all_letters", count = primary_count); + let congrats_msg = t!( + "milestones.congratulations_all_letters", + count = primary_count + ); lines.push(Line::from(Span::styled( format!(" {congrats_msg}"), Style::default() @@ -4214,7 +4390,13 @@ fn render_milestone_overlay( let footer_text = if let Some(ms) = app.post_drill_input_lock_remaining_ms() { format!(" {}", t!("milestones.input_blocked", ms = ms)) } else if milestone_supports_skill_tree_shortcut(milestone) { - format!(" {}", crate::ui::hint::hint(crate::ui::hint::K_T, t!("milestones.hint_skill_tree_continue").as_ref())) + format!( + " {}", + crate::ui::hint::hint( + crate::ui::hint::K_T, + t!("milestones.hint_skill_tree_continue").as_ref() + ) + ) } else { format!(" {}", t!("milestones.hint_any_key")) }; @@ -5756,13 +5938,32 @@ fn render_settings(frame: &mut ratatui::Frame, app: &App) { .as_ref() .map(|(_, input)| input.completion_error) .unwrap_or(false); - let fh_move = crate::ui::hint::hint(crate::ui::hint::K_ARROW_LR, t!("settings.hint_move").as_ref()); - let fh_tab = crate::ui::hint::hint(crate::ui::hint::K_TAB, t!("settings.hint_tab_complete").as_ref()); - let fh_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("settings.hint_confirm").as_ref()); - let fh_cancel = crate::ui::hint::hint(crate::ui::hint::K_ESC, t!("settings.hint_cancel").as_ref()); - let fh_save = crate::ui::hint::hint(crate::ui::hint::K_ESC, t!("settings.hint_save_back").as_ref()); - let fh_change = crate::ui::hint::hint(crate::ui::hint::K_ENTER_ARROWS, t!("settings.hint_change_value").as_ref()); - let fh_edit = crate::ui::hint::hint(crate::ui::hint::K_ENTER_ON_PATH, t!("settings.hint_edit_path").as_ref()); + let fh_move = crate::ui::hint::hint( + crate::ui::hint::K_ARROW_LR, + t!("settings.hint_move").as_ref(), + ); + let fh_tab = crate::ui::hint::hint( + crate::ui::hint::K_TAB, + t!("settings.hint_tab_complete").as_ref(), + ); + let fh_confirm = crate::ui::hint::hint( + crate::ui::hint::K_ENTER, + t!("settings.hint_confirm").as_ref(), + ); + let fh_cancel = + crate::ui::hint::hint(crate::ui::hint::K_ESC, t!("settings.hint_cancel").as_ref()); + let fh_save = crate::ui::hint::hint( + crate::ui::hint::K_ESC, + t!("settings.hint_save_back").as_ref(), + ); + let fh_change = crate::ui::hint::hint( + crate::ui::hint::K_ENTER_ARROWS, + t!("settings.hint_change_value").as_ref(), + ); + let fh_edit = crate::ui::hint::hint( + crate::ui::hint::K_ENTER_ON_PATH, + t!("settings.hint_edit_path").as_ref(), + ); let footer_hints: Vec<&str> = if app.is_editing_path() { let mut hints = vec![ fh_move.as_str(), @@ -5775,11 +5976,7 @@ fn render_settings(frame: &mut ratatui::Frame, app: &App) { } hints } else { - vec![ - fh_save.as_str(), - fh_change.as_str(), - fh_edit.as_str(), - ] + vec![fh_save.as_str(), fh_change.as_str(), fh_edit.as_str()] }; let footer_packed = pack_hint_lines(&footer_hints, inner.width as usize); let footer_height = if inner.height > header_height { @@ -6035,8 +6232,12 @@ fn render_dictionary_language_select(frame: &mut ratatui::Frame, app: &App) { block.render(centered, frame.buffer_mut()); let options = language_packs(); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let footer_hints: Vec<&str> = vec![h_nav.as_str(), h_confirm.as_str(), h_back.as_str()]; let support_notice_t = t!("select.language_resets_layout"); @@ -6079,7 +6280,11 @@ fn render_dictionary_language_select(frame: &mut ratatui::Frame, app: &App) { let is_selected = i == app.dictionary_language_selected; let is_current = pack.language_key == app.config.dictionary_language; let indicator = if is_selected { " > " } else { " " }; - let current_marker_t = if is_current { t!("select.current") } else { std::borrow::Cow::Borrowed("") }; + let current_marker_t = if is_current { + t!("select.current") + } else { + std::borrow::Cow::Borrowed("") + }; let current_marker = current_marker_t.as_ref(); let is_disabled = is_dictionary_language_disabled(app, pack.language_key); let default_layout = @@ -6222,8 +6427,12 @@ fn render_keyboard_layout_select(frame: &mut ratatui::Frame, app: &App) { block.render(centered, frame.buffer_mut()); let options = crate::keyboard::model::KeyboardModel::supported_layout_keys(); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let footer_hints: Vec<&str> = vec![h_nav.as_str(), h_confirm.as_str(), h_back.as_str()]; let support_notice_t = t!("select.layout_no_language_change"); @@ -6266,7 +6475,11 @@ fn render_keyboard_layout_select(frame: &mut ratatui::Frame, app: &App) { let is_selected = i == app.keyboard_layout_selected; let is_current = key == app.config.keyboard_layout; let indicator = if is_selected { " > " } else { " " }; - let current_marker_t = if is_current { t!("select.current") } else { std::borrow::Cow::Borrowed("") }; + let current_marker_t = if is_current { + t!("select.current") + } else { + std::borrow::Cow::Borrowed("") + }; let current_marker = current_marker_t.as_ref(); let validation = validate_language_layout_pair(&app.config.dictionary_language, key); @@ -6352,8 +6565,12 @@ fn render_code_language_select(frame: &mut ratatui::Frame, app: &App) { let options = code_language_options(); let cache_dir = &app.config.code_download_dir; - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN_PGUP_PGDN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN_PGUP_PGDN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let footer_hints: Vec<&str> = vec![h_nav.as_str(), h_confirm.as_str(), h_back.as_str()]; let disabled_notice_t = t!("select.disabled_sources_notice"); @@ -6405,7 +6622,11 @@ fn render_code_language_select(frame: &mut ratatui::Frame, app: &App) { let is_disabled = is_code_language_disabled(app, key); let indicator = if is_selected { " > " } else { " " }; - let current_marker_t = if is_current { t!("select.current") } else { std::borrow::Cow::Borrowed("") }; + let current_marker_t = if is_current { + t!("select.current") + } else { + std::borrow::Cow::Borrowed("") + }; let current_marker = current_marker_t.as_ref(); // Determine availability label @@ -6497,8 +6718,12 @@ fn render_passage_book_select(frame: &mut ratatui::Frame, app: &App) { block.render(centered, frame.buffer_mut()); let options = passage_options(); - let h_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("select.hint_navigate").as_ref()); - let h_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); + let h_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("select.hint_navigate").as_ref(), + ); + let h_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("select.hint_confirm").as_ref()); let h_back = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("select.hint_back").as_ref()); let footer_hints: Vec<&str> = vec![h_nav.as_str(), h_confirm.as_str(), h_back.as_str()]; let disabled_notice_t = t!("select.disabled_sources_notice"); @@ -6706,9 +6931,20 @@ fn render_passage_intro(frame: &mut ratatui::Frame, app: &App) { " ".repeat(width.saturating_sub(fill)) ); let progress_text = if total_bytes > 0 { - format!(" {}", t!("intro.downloading_book_progress", bar = bar, downloaded = done_bytes, total = total_bytes)) + format!( + " {}", + t!( + "intro.downloading_book_progress", + bar = bar, + downloaded = done_bytes, + total = total_bytes + ) + ) } else { - format!(" {}", t!("intro.downloading_book_bytes", bytes = done_bytes)) + format!( + " {}", + t!("intro.downloading_book_bytes", bytes = done_bytes) + ) }; lines.push(Line::from(Span::styled( progress_text, @@ -6717,7 +6953,8 @@ fn render_passage_intro(frame: &mut ratatui::Frame, app: &App) { .add_modifier(Modifier::BOLD), ))); if !app.passage_intro_current_book.is_empty() { - let current_text = t!("intro.current_book", + let current_text = t!( + "intro.current_book", name = app.passage_intro_current_book.as_str(), done = done_books.saturating_add(1).min(total_books), total = total_books @@ -6731,16 +6968,30 @@ fn render_passage_intro(frame: &mut ratatui::Frame, app: &App) { let hint_lines = if app.passage_intro_downloading { Vec::new() } else { - let ih_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("intro.hint_navigate").as_ref()); - let ih_adj = crate::ui::hint::hint(crate::ui::hint::K_LEFT_RIGHT, t!("intro.hint_adjust").as_ref()); - let ih_edit = crate::ui::hint::hint(crate::ui::hint::K_TYPE_BACKSPACE, t!("intro.hint_edit").as_ref()); - let ih_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); - let ih_cancel = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); - let hints: Vec<&str> = vec![ih_nav.as_str(), ih_adj.as_str(), ih_edit.as_str(), ih_confirm.as_str(), ih_cancel.as_str()]; - pack_hint_lines( - &hints, - inner.width as usize, - ) + let ih_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("intro.hint_navigate").as_ref(), + ); + let ih_adj = crate::ui::hint::hint( + crate::ui::hint::K_LEFT_RIGHT, + t!("intro.hint_adjust").as_ref(), + ); + let ih_edit = crate::ui::hint::hint( + crate::ui::hint::K_TYPE_BACKSPACE, + t!("intro.hint_edit").as_ref(), + ); + let ih_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); + let ih_cancel = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); + let hints: Vec<&str> = vec![ + ih_nav.as_str(), + ih_adj.as_str(), + ih_edit.as_str(), + ih_confirm.as_str(), + ih_cancel.as_str(), + ]; + pack_hint_lines(&hints, inner.width as usize) }; let footer_height = if hint_lines.is_empty() { 0 @@ -6817,7 +7068,13 @@ fn render_passage_download_progress(frame: &mut ratatui::Frame, app: &App) { Line::from(""), Line::from(Span::styled( if total_bytes > 0 { - t!("intro.progress_bytes", name = bar, downloaded = done_bytes, total = total_bytes).to_string() + t!( + "intro.progress_bytes", + name = bar, + downloaded = done_bytes, + total = total_bytes + ) + .to_string() } else { t!("intro.downloaded_bytes", bytes = done_bytes).to_string() }, @@ -6942,9 +7199,20 @@ fn render_code_intro(frame: &mut ratatui::Frame, app: &App) { " ".repeat(width.saturating_sub(fill)) ); let progress_text = if total_bytes > 0 { - format!(" {}", t!("intro.downloading_code_progress", bar = bar, downloaded = done_bytes, total = total_bytes)) + format!( + " {}", + t!( + "intro.downloading_code_progress", + bar = bar, + downloaded = done_bytes, + total = total_bytes + ) + ) } else { - format!(" {}", t!("intro.downloading_code_bytes", bytes = done_bytes)) + format!( + " {}", + t!("intro.downloading_code_bytes", bytes = done_bytes) + ) }; lines.push(Line::from(Span::styled( progress_text, @@ -6953,7 +7221,8 @@ fn render_code_intro(frame: &mut ratatui::Frame, app: &App) { .add_modifier(Modifier::BOLD), ))); if !app.code_intro_current_repo.is_empty() { - let current_text = t!("intro.current_repo", + let current_text = t!( + "intro.current_repo", name = app.code_intro_current_repo.as_str(), done = done_repos.saturating_add(1).min(total_repos), total = total_repos @@ -6967,16 +7236,30 @@ fn render_code_intro(frame: &mut ratatui::Frame, app: &App) { let hint_lines = if app.code_intro_downloading { Vec::new() } else { - let ih_nav = crate::ui::hint::hint(crate::ui::hint::K_UP_DOWN, t!("intro.hint_navigate").as_ref()); - let ih_adj = crate::ui::hint::hint(crate::ui::hint::K_LEFT_RIGHT, t!("intro.hint_adjust").as_ref()); - let ih_edit = crate::ui::hint::hint(crate::ui::hint::K_TYPE_BACKSPACE, t!("intro.hint_edit").as_ref()); - let ih_confirm = crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); - let ih_cancel = crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); - let hints: Vec<&str> = vec![ih_nav.as_str(), ih_adj.as_str(), ih_edit.as_str(), ih_confirm.as_str(), ih_cancel.as_str()]; - pack_hint_lines( - &hints, - inner.width as usize, - ) + let ih_nav = crate::ui::hint::hint( + crate::ui::hint::K_UP_DOWN, + t!("intro.hint_navigate").as_ref(), + ); + let ih_adj = crate::ui::hint::hint( + crate::ui::hint::K_LEFT_RIGHT, + t!("intro.hint_adjust").as_ref(), + ); + let ih_edit = crate::ui::hint::hint( + crate::ui::hint::K_TYPE_BACKSPACE, + t!("intro.hint_edit").as_ref(), + ); + let ih_confirm = + crate::ui::hint::hint(crate::ui::hint::K_ENTER, t!("intro.hint_confirm").as_ref()); + let ih_cancel = + crate::ui::hint::hint(crate::ui::hint::K_Q_ESC, t!("intro.hint_cancel").as_ref()); + let hints: Vec<&str> = vec![ + ih_nav.as_str(), + ih_adj.as_str(), + ih_edit.as_str(), + ih_confirm.as_str(), + ih_cancel.as_str(), + ]; + pack_hint_lines(&hints, inner.width as usize) }; let footer_height = if hint_lines.is_empty() { 0 @@ -7054,7 +7337,13 @@ fn render_code_download_progress(frame: &mut ratatui::Frame, app: &App) { Line::from(""), Line::from(Span::styled( if total_bytes > 0 { - t!("intro.progress_bytes", name = bar, downloaded = done_bytes, total = total_bytes).to_string() + t!( + "intro.progress_bytes", + name = bar, + downloaded = done_bytes, + total = total_bytes + ) + .to_string() } else { t!("intro.downloaded_bytes", bytes = done_bytes).to_string() }, @@ -7088,7 +7377,8 @@ fn render_skill_tree(frame: &mut ratatui::Frame, app: &App) { let sentence_one = sentence_one_t.as_ref(); let sentence_two_t = t!("skill_tree.unlock_msg_2"); let sentence_two = sentence_two_t.as_ref(); - let branch_name = crate::engine::skill_tree::get_branch_definition(branch_id).display_name(); + let branch_name = + crate::engine::skill_tree::get_branch_definition(branch_id).display_name(); let dialog_width = 72u16.min(area.width.saturating_sub(4)); let content_width = dialog_width.saturating_sub(6).max(1) as usize; // border + side margins let body_required = 4 // blank + title + blank + blank-between-sentences @@ -7421,8 +7711,12 @@ fn render_keyboard_detail_panel(frame: &mut ratatui::Frame, app: &App, area: Rec t!("keyboard.no_data_short").to_string() }; - let branch_info = find_key_branch(selected) - .map(|(branch, level, pos)| (branch.display_name(), format!("{} (key #{pos})", level.display_name()))); + let branch_info = find_key_branch(selected).map(|(branch, level, pos)| { + ( + branch.display_name(), + format!("{} (key #{pos})", level.display_name()), + ) + }); // Ranked-only mastery display (same semantics as skill tree per-key progress) let ranked_conf = app.ranked_key_stats.get_confidence(selected).min(1.0); @@ -7436,12 +7730,32 @@ fn render_keyboard_detail_panel(frame: &mut ratatui::Frame, app: &App, area: Rec let mastery_text = format!("{mastery_bar} {:>3.0}%", ranked_conf * 100.0); let mut left_col: Vec = vec![ - format!("{}{}", t!("keyboard.finger_label"), finger.localized_description()), + format!( + "{}{}", + t!("keyboard.finger_label"), + finger.localized_description() + ), format!("{}{shift_guidance}", t!("keyboard.shift_label")), - format!("{}{}", t!("keyboard.overall_avg_time"), fmt_avg_time(overall_stat)), - format!("{}{}", t!("keyboard.overall_best_time"), fmt_best_time(overall_stat)), - format!("{}{}", t!("keyboard.overall_samples"), fmt_samples(overall_stat)), - format!("{}{}", t!("keyboard.overall_accuracy_label"), fmt_acc(overall_acc)), + format!( + "{}{}", + t!("keyboard.overall_avg_time"), + fmt_avg_time(overall_stat) + ), + format!( + "{}{}", + t!("keyboard.overall_best_time"), + fmt_best_time(overall_stat) + ), + format!( + "{}{}", + t!("keyboard.overall_samples"), + fmt_samples(overall_stat) + ), + format!( + "{}{}", + t!("keyboard.overall_accuracy_label"), + fmt_acc(overall_acc) + ), ]; let mut right_col: Vec = Vec::new(); @@ -7456,24 +7770,52 @@ fn render_keyboard_detail_panel(frame: &mut ratatui::Frame, app: &App, area: Rec right_col.push(format!( "{}{}", t!("keyboard.unlocked_label"), - if is_unlocked { yes_t.as_ref() } else { no_t.as_ref() } + if is_unlocked { + yes_t.as_ref() + } else { + no_t.as_ref() + } )); let yes_t2 = t!("keyboard.yes"); let no_t2 = t!("keyboard.no"); right_col.push(format!( "{}{}", t!("keyboard.in_focus_label"), - if in_focus { yes_t2.as_ref() } else { no_t2.as_ref() } + if in_focus { + yes_t2.as_ref() + } else { + no_t2.as_ref() + } )); if is_unlocked { right_col.push(format!("{}{mastery_text}", t!("keyboard.mastery_label"))); } else { - right_col.push(format!("{}{}", t!("keyboard.mastery_label"), t!("keyboard.mastery_locked"))); + right_col.push(format!( + "{}{}", + t!("keyboard.mastery_label"), + t!("keyboard.mastery_locked") + )); } - right_col.push(format!("{}{}", t!("keyboard.ranked_avg_time"), fmt_avg_time(ranked_stat))); - right_col.push(format!("{}{}", t!("keyboard.ranked_best_time"), fmt_best_time(ranked_stat))); - right_col.push(format!("{}{}", t!("keyboard.ranked_samples"), fmt_samples(ranked_stat))); - right_col.push(format!("{}{}", t!("keyboard.ranked_accuracy_label"), fmt_acc(ranked_acc))); + right_col.push(format!( + "{}{}", + t!("keyboard.ranked_avg_time"), + fmt_avg_time(ranked_stat) + )); + right_col.push(format!( + "{}{}", + t!("keyboard.ranked_best_time"), + fmt_best_time(ranked_stat) + )); + right_col.push(format!( + "{}{}", + t!("keyboard.ranked_samples"), + fmt_samples(ranked_stat) + )); + right_col.push(format!( + "{}{}", + t!("keyboard.ranked_accuracy_label"), + fmt_acc(ranked_acc) + )); if left_col.is_empty() { left_col.push(t!("keyboard.no_data").to_string());