@@ -20,7 +20,9 @@ use crate::{layout::Flex, prelude::*, style::Styled, widgets::Block};
20
20
/// [`Table`] implements [`Widget`] and so it can be drawn using [`Frame::render_widget`].
21
21
///
22
22
/// [`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.
24
26
///
25
27
/// Note: if the `widths` field is empty, the table will be rendered with equal widths.
26
28
///
@@ -775,7 +777,14 @@ impl Table<'_> {
775
777
end += 1 ;
776
778
}
777
779
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
779
788
while selected >= end {
780
789
height = height. saturating_add ( self . rows [ end] . height_with_margin ( ) ) ;
781
790
end += 1 ;
@@ -784,6 +793,8 @@ impl Table<'_> {
784
793
start += 1 ;
785
794
}
786
795
}
796
+
797
+ // scroll up until the selected row is visible
787
798
while selected < start {
788
799
start -= 1 ;
789
800
height = height. saturating_add ( self . rows [ start] . height_with_margin ( ) ) ;
@@ -1007,6 +1018,8 @@ mod tests {
1007
1018
1008
1019
#[ cfg( test) ]
1009
1020
mod render {
1021
+ use rstest:: rstest;
1022
+
1010
1023
use super :: * ;
1011
1024
1012
1025
#[ test]
@@ -1197,6 +1210,36 @@ mod tests {
1197
1210
] ) ;
1198
1211
assert_eq ! ( buf, expected) ;
1199
1212
}
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
+ }
1200
1243
}
1201
1244
1202
1245
// test how constraints interact with table column width allocation
0 commit comments