Skip to content

Commit 127d706

Browse files
authored
fix(table): ensure render offset without selection properly (#1187)
Fixes: <#1179>
1 parent 1365620 commit 127d706

File tree

1 file changed

+45
-2
lines changed

1 file changed

+45
-2
lines changed

src/widgets/table/table.rs

+45-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ use crate::{layout::Flex, prelude::*, style::Styled, widgets::Block};
2020
/// [`Table`] implements [`Widget`] and so it can be drawn using [`Frame::render_widget`].
2121
///
2222
/// [`Table`] is also a [`StatefulWidget`], which means you can use it with [`TableState`] to allow
23-
/// the user to scroll through the rows and select one of them.
23+
/// the user to scroll through the rows and select one of them. When rendering a [`Table`] with a
24+
/// [`TableState`], the selected row will be highlighted. If the selected row is not visible (based
25+
/// on the offset), the table will be scrolled to make the selected row visible.
2426
///
2527
/// Note: if the `widths` field is empty, the table will be rendered with equal widths.
2628
///
@@ -775,7 +777,14 @@ impl Table<'_> {
775777
end += 1;
776778
}
777779

778-
let selected = selected.unwrap_or(0).min(self.rows.len() - 1);
780+
let Some(selected) = selected else {
781+
return (start, end);
782+
};
783+
784+
// clamp the selected row to the last row
785+
let selected = selected.min(self.rows.len() - 1);
786+
787+
// scroll down until the selected row is visible
779788
while selected >= end {
780789
height = height.saturating_add(self.rows[end].height_with_margin());
781790
end += 1;
@@ -784,6 +793,8 @@ impl Table<'_> {
784793
start += 1;
785794
}
786795
}
796+
797+
// scroll up until the selected row is visible
787798
while selected < start {
788799
start -= 1;
789800
height = height.saturating_add(self.rows[start].height_with_margin());
@@ -1007,6 +1018,8 @@ mod tests {
10071018

10081019
#[cfg(test)]
10091020
mod render {
1021+
use rstest::rstest;
1022+
10101023
use super::*;
10111024

10121025
#[test]
@@ -1197,6 +1210,36 @@ mod tests {
11971210
]);
11981211
assert_eq!(buf, expected);
11991212
}
1213+
1214+
/// Note that this includes a regression test for a bug where the table would not render the
1215+
/// correct rows when there is no selection.
1216+
/// <https://github.com/ratatui-org/ratatui/issues/1179>
1217+
#[rstest]
1218+
#[case::no_selection(None, 50, ["50", "51", "52", "53", "54"])]
1219+
#[case::selection_before_offset(20, 20, ["20", "21", "22", "23", "24"])]
1220+
#[case::selection_immediately_before_offset(49, 49, ["49", "50", "51", "52", "53"])]
1221+
#[case::selection_at_start_of_offset(50, 50, ["50", "51", "52", "53", "54"])]
1222+
#[case::selection_at_end_of_offset(54, 50, ["50", "51", "52", "53", "54"])]
1223+
#[case::selection_immediately_after_offset(55, 51, ["51", "52", "53", "54", "55"])]
1224+
#[case::selection_after_offset(80, 76, ["76", "77", "78", "79", "80"])]
1225+
fn render_with_selection_and_offset<T: Into<Option<usize>>>(
1226+
#[case] selected_row: T,
1227+
#[case] expected_offset: usize,
1228+
#[case] expected_items: [&str; 5],
1229+
) {
1230+
// render 100 rows offset at 50, with a selected row
1231+
let rows = (0..100).map(|i| Row::new([i.to_string()]));
1232+
let table = Table::new(rows, [Constraint::Length(2)]);
1233+
let mut buf = Buffer::empty(Rect::new(0, 0, 2, 5));
1234+
let mut state = TableState::new()
1235+
.with_offset(50)
1236+
.with_selected(selected_row);
1237+
1238+
StatefulWidget::render(table.clone(), Rect::new(0, 0, 5, 5), &mut buf, &mut state);
1239+
1240+
assert_eq!(buf, Buffer::with_lines(expected_items));
1241+
assert_eq!(state.offset, expected_offset);
1242+
}
12001243
}
12011244

12021245
// test how constraints interact with table column width allocation

0 commit comments

Comments
 (0)