Skip to content

Commit 6929767

Browse files
committed
fix(tui): prevent underflow in dropdown navigation and scroll calculations
Fixes #5182, #5180, #5176 - Add guard for max_visible=0 in dropdown select_next/select_prev - Use saturating_sub in scroll.ensure_visible to prevent underflow - Add bounds checking in scrollable_dropdown visible_items - Guard against max_visible=0 in select_next/select_prev/calculate_scroll_offset
1 parent 66f1b2c commit 6929767

File tree

3 files changed

+18
-11
lines changed

3 files changed

+18
-11
lines changed

src/cortex-tui-components/src/dropdown.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ impl DropdownState {
9999

100100
/// Select the next item.
101101
pub fn select_next(&mut self) {
102-
if self.items.is_empty() {
102+
if self.items.is_empty() || self.max_visible == 0 {
103103
return;
104104
}
105105
self.selected = (self.selected + 1) % self.items.len();
@@ -108,7 +108,7 @@ impl DropdownState {
108108

109109
/// Select the previous item.
110110
pub fn select_prev(&mut self) {
111-
if self.items.is_empty() {
111+
if self.items.is_empty() || self.max_visible == 0 {
112112
return;
113113
}
114114
self.selected = if self.selected == 0 {

src/cortex-tui-components/src/scroll.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,16 @@ impl ScrollState {
119119
///
120120
/// Adjusts offset if necessary to make the item visible.
121121
pub fn ensure_visible(&mut self, index: usize) {
122+
// Guard against zero visible items to prevent underflow
123+
if self.visible == 0 {
124+
return;
125+
}
122126
if index < self.offset {
123127
// Item is above visible area - scroll up
124128
self.offset = index;
125129
} else if index >= self.offset + self.visible {
126130
// Item is below visible area - scroll down
127-
self.offset = index.saturating_sub(self.visible - 1);
131+
self.offset = index.saturating_sub(self.visible.saturating_sub(1));
128132
}
129133
self.clamp_offset();
130134
}

src/cortex-tui/src/widgets/scrollable_dropdown.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,12 @@ impl<'a> ScrollableDropdown<'a> {
254254

255255
/// Returns visible items slice.
256256
fn visible_items(&self) -> &[DropdownItem] {
257-
let start = self.scroll_offset;
257+
if self.max_visible == 0 || self.items.is_empty() {
258+
return &[];
259+
}
260+
let start = self.scroll_offset.min(self.items.len());
258261
let end = (start + self.max_visible).min(self.items.len());
259-
&self.items[start..end]
262+
self.items.get(start..end).unwrap_or(&[])
260263
}
261264

262265
/// Renders a single item.
@@ -459,7 +462,7 @@ pub fn calculate_scroll_offset(
459462
max_visible: usize,
460463
total_items: usize,
461464
) -> usize {
462-
if total_items <= max_visible {
465+
if max_visible == 0 || total_items <= max_visible {
463466
return 0;
464467
}
465468

@@ -468,7 +471,7 @@ pub fn calculate_scroll_offset(
468471
selected
469472
} else if selected >= current_offset + max_visible {
470473
// Selected item is below visible area - scroll down
471-
selected.saturating_sub(max_visible - 1)
474+
selected.saturating_sub(max_visible.saturating_sub(1))
472475
} else {
473476
// Selected item is visible - no change needed
474477
current_offset
@@ -482,7 +485,7 @@ pub fn select_prev(
482485
max_visible: usize,
483486
total_items: usize,
484487
) -> (usize, usize) {
485-
if total_items == 0 {
488+
if total_items == 0 || max_visible == 0 {
486489
return (0, 0);
487490
}
488491

@@ -511,7 +514,7 @@ pub fn select_next(
511514
max_visible: usize,
512515
total_items: usize,
513516
) -> (usize, usize) {
514-
if total_items == 0 {
517+
if total_items == 0 || max_visible == 0 {
515518
return (0, 0);
516519
}
517520

@@ -521,8 +524,8 @@ pub fn select_next(
521524
// Wrapped to start
522525
0
523526
} else if new_selected >= scroll_offset + max_visible {
524-
// Need to scroll down
525-
new_selected.saturating_sub(max_visible - 1)
527+
// Need to scroll down - use saturating_sub to prevent underflow
528+
new_selected.saturating_sub(max_visible.saturating_sub(1))
526529
} else {
527530
scroll_offset
528531
};

0 commit comments

Comments
 (0)