sql_cli/ui/rendering/
ui_layout_utils.rs

1//! UI layout calculation utilities
2//!
3//! This module contains pure utility functions for calculating UI dimensions,
4//! scrolling, and layout-related operations without coupling to the main TUI state.
5
6// UI Layout Constants (from enhanced_tui.rs)
7const TABLE_BORDER_WIDTH: u16 = 4; // Left border (1) + right border (1) + padding (2)
8const INPUT_AREA_HEIGHT: u16 = 3; // Height of the command input area
9const STATUS_BAR_HEIGHT: u16 = 3; // Height of the status bar
10const TOTAL_UI_CHROME: u16 = INPUT_AREA_HEIGHT + STATUS_BAR_HEIGHT; // Total non-table UI height
11const TABLE_CHROME_ROWS: u16 = 3; // Table header (1) + top border (1) + bottom border (1)
12
13/// Calculate the number of data rows available for display in the terminal
14/// This accounts for all UI chrome including input area, status bar, table header, and borders
15pub fn calculate_available_data_rows(terminal_height: u16) -> u16 {
16    terminal_height
17        .saturating_sub(TOTAL_UI_CHROME) // Remove input area and status bar
18        .saturating_sub(TABLE_CHROME_ROWS) // Remove table header and borders
19}
20
21/// Calculate the number of data rows available for a table area
22/// This accounts only for table chrome (header, borders)
23pub fn calculate_table_data_rows(table_area_height: u16) -> u16 {
24    table_area_height.saturating_sub(TABLE_CHROME_ROWS)
25}
26
27/// Extract timing information from debug strings
28/// Parses strings like "total=123µs" or "total=1.5ms" and returns milliseconds as f64
29pub fn extract_timing_from_debug_string(s: &str) -> Option<f64> {
30    if let Some(total_pos) = s.find("total=") {
31        let after_total = &s[total_pos + 6..];
32        let time_str =
33            if let Some(end_pos) = after_total.find(',').or_else(|| after_total.find(')')) {
34                &after_total[..end_pos]
35            } else {
36                after_total
37            };
38
39        if let Some(us_pos) = time_str.find("µs") {
40            time_str[..us_pos].parse::<f64>().ok().map(|us| us / 1000.0)
41        } else if let Some(ms_pos) = time_str.find("ms") {
42            time_str[..ms_pos].parse::<f64>().ok()
43        } else if let Some(s_pos) = time_str.find('s') {
44            time_str[..s_pos].parse::<f64>().ok().map(|s| s * 1000.0)
45        } else {
46            None
47        }
48    } else {
49        None
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_calculate_available_data_rows() {
59        // Terminal height 50 - UI chrome (6) - table chrome (3) = 41 rows
60        assert_eq!(calculate_available_data_rows(50), 41);
61
62        // Edge case: very small terminal
63        assert_eq!(calculate_available_data_rows(5), 0); // saturating_sub prevents underflow
64    }
65
66    #[test]
67    fn test_calculate_table_data_rows() {
68        // Table area height 20 - table chrome (3) = 17 rows
69        assert_eq!(calculate_table_data_rows(20), 17);
70
71        // Edge case: table area smaller than chrome
72        assert_eq!(calculate_table_data_rows(2), 0);
73    }
74
75    #[test]
76    fn test_extract_timing_from_debug_string() {
77        // Test microseconds conversion to milliseconds
78        assert_eq!(
79            extract_timing_from_debug_string("query executed total=1500µs"),
80            Some(1.5)
81        );
82
83        // Test milliseconds
84        assert_eq!(
85            extract_timing_from_debug_string("query executed total=2.5ms"),
86            Some(2.5)
87        );
88
89        // Test seconds conversion to milliseconds
90        assert_eq!(
91            extract_timing_from_debug_string("query executed total=1.2s"),
92            Some(1200.0)
93        );
94
95        // Test with comma separator
96        assert_eq!(
97            extract_timing_from_debug_string("query executed total=800µs, other=123"),
98            Some(0.8)
99        );
100
101        // Test with parentheses
102        assert_eq!(
103            extract_timing_from_debug_string("query executed (total=300µs)"),
104            Some(0.3)
105        );
106
107        // Test invalid input
108        assert_eq!(extract_timing_from_debug_string("no timing info"), None);
109        assert_eq!(extract_timing_from_debug_string("total=invalid"), None);
110    }
111}