diff --git a/src/command/keymap.rs b/src/command/keymap.rs index e84357e..d5564c7 100644 --- a/src/command/keymap.rs +++ b/src/command/keymap.rs @@ -266,10 +266,14 @@ impl KeymapSet { normal.bind(KeyCode::Char('G'), none, "jump-last-row"); normal.bind(KeyCode::Char('0'), none, "jump-first-col"); normal.bind(KeyCode::Char('$'), none, "jump-last-col"); + normal.bind(KeyCode::Home, none, "jump-first-col"); + normal.bind(KeyCode::End, none, "jump-last-col"); // Scroll normal.bind_args(KeyCode::Char('d'), ctrl, "scroll-rows", vec!["5".into()]); normal.bind_args(KeyCode::Char('u'), ctrl, "scroll-rows", vec!["-5".into()]); + normal.bind_args(KeyCode::PageDown, none, "page-scroll", vec!["1".into()]); + normal.bind_args(KeyCode::PageUp, none, "page-scroll", vec!["-1".into()]); // Cell operations normal.bind(KeyCode::Char('x'), none, "clear-cell"); @@ -357,7 +361,7 @@ impl KeymapSet { // Drill into aggregated cell / view history / add row normal.bind(KeyCode::Char('>'), none, "drill-into-cell"); normal.bind(KeyCode::Char('<'), none, "view-back"); - normal.bind(KeyCode::Char('o'), none, "add-record-row"); + normal.bind(KeyCode::Char('o'), none, "open-record-row"); // Records mode toggle and prune toggle normal.bind(KeyCode::Char('R'), none, "toggle-records-mode"); @@ -560,6 +564,7 @@ impl KeymapSet { let mut ed = Keymap::new(); ed.bind_args(KeyCode::Esc, none, "enter-mode", vec!["normal".into()]); ed.bind(KeyCode::Enter, none, "commit-cell-edit"); + ed.bind(KeyCode::Tab, none, "commit-and-advance-right"); ed.bind_args(KeyCode::Backspace, none, "pop-char", vec!["edit".into()]); ed.bind_any_char("append-char", vec!["edit".into()]); set.insert(ModeKey::Editing, Arc::new(ed)); diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..ed31509 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,50 @@ +use crate::model::cell::CellValue; + +/// Format a CellValue for display with number formatting options. +pub fn format_value(v: Option<&CellValue>, comma: bool, decimals: u8) -> String { + match v { + Some(CellValue::Number(n)) => format_f64(*n, comma, decimals), + Some(CellValue::Text(s)) => s.clone(), + None => String::new(), + } +} + +/// Parse a number format string like ",.0" into (use_commas, decimal_places). +pub fn parse_number_format(fmt: &str) -> (bool, u8) { + let comma = fmt.contains(','); + let decimals = fmt + .rfind('.') + .and_then(|i| fmt[i + 1..].parse::().ok()) + .unwrap_or(0); + (comma, decimals) +} + +/// Format an f64 with optional comma grouping and decimal places. +pub fn format_f64(n: f64, comma: bool, decimals: u8) -> String { + let formatted = format!("{:.prec$}", n, prec = decimals as usize); + if !comma { + return formatted; + } + let (int_part, dec_part) = if let Some(dot) = formatted.find('.') { + (&formatted[..dot], Some(&formatted[dot..])) + } else { + (&formatted[..], None) + }; + let is_neg = int_part.starts_with('-'); + let digits = if is_neg { &int_part[1..] } else { int_part }; + let mut result = String::new(); + for (idx, c) in digits.chars().rev().enumerate() { + if idx > 0 && idx % 3 == 0 { + result.push(','); + } + result.push(c); + } + if is_neg { + result.push('-'); + } + let mut out: String = result.chars().rev().collect(); + if let Some(dec) = dec_part { + out.push_str(dec); + } + out +} diff --git a/src/main.rs b/src/main.rs index b1d1adf..642f343 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod command; mod draw; +mod format; mod formula; mod import; mod model;