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