sql_cli/ui/utils/
scroll_utils.rs

1/// Scroll and cursor position utilities extracted from enhanced_tui
2/// Calculate horizontal scroll offset for input field
3pub fn calculate_horizontal_scroll(cursor_pos: usize, terminal_width: u16) -> u16 {
4    let inner_width = terminal_width.saturating_sub(3) as usize; // Account for borders + padding
5
6    if cursor_pos >= inner_width {
7        // Cursor is beyond visible area, scroll to show it
8        (cursor_pos + 1).saturating_sub(inner_width) as u16
9    } else {
10        0
11    }
12}
13
14/// Update scroll offset based on cursor position
15pub fn update_scroll_offset(cursor_pos: usize, current_offset: u16, visible_width: usize) -> u16 {
16    // If cursor is before the scroll window, scroll left
17    if cursor_pos < current_offset as usize {
18        cursor_pos as u16
19    }
20    // If cursor is after the scroll window, scroll right
21    else if cursor_pos >= current_offset as usize + visible_width {
22        (cursor_pos + 1).saturating_sub(visible_width) as u16
23    }
24    // Otherwise keep current offset
25    else {
26        current_offset
27    }
28}
29
30/// Calculate visible range for viewport
31pub fn calculate_visible_range(
32    total_items: usize,
33    viewport_start: usize,
34    viewport_size: usize,
35) -> (usize, usize) {
36    let start = viewport_start.min(total_items);
37    let end = (viewport_start + viewport_size).min(total_items);
38    (start, end)
39}
40
41/// Calculate page jump offset
42pub fn calculate_page_jump(
43    current_pos: usize,
44    page_size: usize,
45    total_items: usize,
46    direction_up: bool,
47) -> usize {
48    if direction_up {
49        current_pos.saturating_sub(page_size)
50    } else {
51        (current_pos + page_size).min(total_items.saturating_sub(1))
52    }
53}
54
55/// Calculate centered viewport position
56/// Centers the given position in the viewport if possible
57pub fn calculate_centered_viewport(
58    target_pos: usize,
59    viewport_size: usize,
60    total_items: usize,
61) -> usize {
62    if total_items <= viewport_size {
63        // Everything fits, start at 0
64        0
65    } else if target_pos < viewport_size / 2 {
66        // Target is near the beginning
67        0
68    } else if target_pos > total_items.saturating_sub(viewport_size / 2) {
69        // Target is near the end
70        total_items.saturating_sub(viewport_size)
71    } else {
72        // Center the target
73        target_pos.saturating_sub(viewport_size / 2)
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_horizontal_scroll() {
83        // No scroll needed
84        assert_eq!(calculate_horizontal_scroll(5, 80), 0);
85
86        // Scroll needed
87        assert_eq!(calculate_horizontal_scroll(100, 80), 24); // 100 + 1 - 77
88    }
89
90    #[test]
91    fn test_update_scroll_offset() {
92        // Cursor before window - scroll left
93        assert_eq!(update_scroll_offset(5, 10, 20), 5);
94
95        // Cursor in window - no change
96        assert_eq!(update_scroll_offset(15, 10, 20), 10);
97
98        // Cursor after window - scroll right
99        assert_eq!(update_scroll_offset(35, 10, 20), 16); // 35 + 1 - 20
100    }
101
102    #[test]
103    fn test_visible_range() {
104        assert_eq!(calculate_visible_range(100, 10, 20), (10, 30));
105        assert_eq!(calculate_visible_range(100, 90, 20), (90, 100));
106        assert_eq!(calculate_visible_range(5, 0, 10), (0, 5));
107    }
108
109    #[test]
110    fn test_page_jump() {
111        // Jump up
112        assert_eq!(calculate_page_jump(50, 10, 100, true), 40);
113        assert_eq!(calculate_page_jump(5, 10, 100, true), 0);
114
115        // Jump down
116        assert_eq!(calculate_page_jump(50, 10, 100, false), 60);
117        assert_eq!(calculate_page_jump(95, 10, 100, false), 99);
118    }
119
120    #[test]
121    fn test_centered_viewport() {
122        // Everything fits
123        assert_eq!(calculate_centered_viewport(5, 20, 10), 0);
124
125        // Near beginning
126        assert_eq!(calculate_centered_viewport(5, 20, 100), 0);
127
128        // Near end
129        assert_eq!(calculate_centered_viewport(95, 20, 100), 80);
130
131        // Middle - centered
132        assert_eq!(calculate_centered_viewport(50, 20, 100), 40);
133    }
134}