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