Skip to content

Commit 9b86760

Browse files
factorydroidechobt
authored andcommitted
fix(cortex-tui): ensure settings list scrolls correctly in small terminals
Fixes bounty issue #1709 The interactive list renderer now computes the correct scroll offset at render time based on the actual viewport height. Previously, scrolling used only the max_visible value which could be larger than the terminal window, causing items to not be visible when selected.
1 parent 5c6fea4 commit 9b86760

File tree

1 file changed

+54
-2
lines changed

1 file changed

+54
-2
lines changed

cortex-tui/src/interactive/renderer.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,23 @@ impl<'a> InteractiveWidget<'a> {
123123
/// Render the list items.
124124
fn render_items(&self, area: Rect, buf: &mut Buffer) {
125125
let visible_items = self.state.visible_items();
126-
let start = self.state.scroll_offset;
127-
let end = (start + area.height as usize).min(visible_items.len());
126+
let viewport_height = area.height as usize;
127+
128+
// Compute the effective scroll offset to ensure the selected item is visible.
129+
// This handles cases where the actual viewport is smaller than max_visible,
130+
// which can occur in small terminal windows.
131+
let start = if self.state.selected >= self.state.scroll_offset + viewport_height {
132+
// Selected item is below the visible area - scroll down
133+
self.state.selected.saturating_sub(viewport_height - 1)
134+
} else if self.state.selected < self.state.scroll_offset {
135+
// Selected item is above the visible area - scroll up
136+
self.state.selected
137+
} else {
138+
// Selected item is within view - use existing scroll offset
139+
self.state.scroll_offset
140+
};
141+
142+
let end = (start + viewport_height).min(visible_items.len());
128143

129144
for (i, (real_idx, item)) in visible_items
130145
.iter()
@@ -329,4 +344,41 @@ mod tests {
329344
// 2 items + 1 title + 1 search + 1 hints + 2 border = 7
330345
assert_eq!(widget.required_height(), 7);
331346
}
347+
348+
#[test]
349+
fn test_scroll_offset_calculation_small_viewport() {
350+
// Test that scroll offset is computed correctly when viewport is smaller than max_visible.
351+
// This tests the fix for issue #1709 where items couldn't be scrolled to in small terminals.
352+
let items: Vec<InteractiveItem> = (0..20)
353+
.map(|i| InteractiveItem::new(format!("{}", i), format!("Item {}", i)))
354+
.collect();
355+
356+
let mut state =
357+
InteractiveState::new("Test", items, InteractiveAction::Custom("test".into()))
358+
.with_max_visible(25); // max_visible is 25, but viewport will be smaller
359+
360+
// Select the last item (index 19)
361+
for _ in 0..19 {
362+
state.select_next();
363+
}
364+
assert_eq!(state.selected, 19);
365+
366+
// Simulate a small viewport of height 5
367+
let viewport_height: usize = 5;
368+
369+
// Calculate start as the renderer would
370+
let start = if state.selected >= state.scroll_offset + viewport_height {
371+
state.selected.saturating_sub(viewport_height - 1)
372+
} else if state.selected < state.scroll_offset {
373+
state.selected
374+
} else {
375+
state.scroll_offset
376+
};
377+
378+
// With selected=19 and viewport_height=5, start should be 15
379+
// so items 15-19 are visible, including the selected item 19
380+
assert_eq!(start, 15);
381+
assert!(state.selected >= start);
382+
assert!(state.selected < start + viewport_height);
383+
}
332384
}

0 commit comments

Comments
 (0)