Skip to main content

ratatui_widgets/
table.rs

1//! The [`Table`] widget is used to display multiple rows and columns in a grid and allows selecting
2//! one or multiple cells.
3
4use alloc::vec;
5use alloc::vec::Vec;
6
7use itertools::Itertools;
8use ratatui_core::buffer::Buffer;
9use ratatui_core::layout::{Constraint, Flex, Layout, Rect};
10use ratatui_core::style::{Style, Styled};
11use ratatui_core::text::Text;
12use ratatui_core::widgets::{StatefulWidget, Widget};
13
14pub use self::cell::Cell;
15pub use self::highlight_spacing::HighlightSpacing;
16pub use self::row::Row;
17pub use self::state::TableState;
18use crate::block::{Block, BlockExt};
19
20mod cell;
21mod highlight_spacing;
22mod row;
23mod state;
24
25/// A widget to display data in formatted columns.
26///
27/// A `Table` is a collection of [`Row`]s, each composed of [`Cell`]s:
28///
29/// You can construct a [`Table`] using either [`Table::new`] or [`Table::default`] and then chain
30/// builder style methods to set the desired properties.
31///
32/// Table cells can be aligned, for more details see [`Cell`].
33///
34/// Make sure to call the [`Table::widths`] method, otherwise the columns will all have a width of 0
35/// and thus not be visible.
36///
37/// [`Table`] implements [`Widget`] and so it can be drawn using `Frame::render_widget`.
38///
39/// [`Table`] is also a [`StatefulWidget`], which means you can use it with [`TableState`] to allow
40/// the user to scroll through the rows and select one of them. When rendering a [`Table`] with a
41/// [`TableState`], the selected row, column and cell will be highlighted. If the selected row is
42/// not visible (based on the offset), the table will be scrolled to make the selected row visible.
43///
44/// Note: if the `widths` field is empty, the table will be rendered with equal widths.
45/// Note: Highlight styles are applied in the following order: Row, Column, Cell.
46///
47/// See the table example and the recipe and traceroute tabs in the demo2 example in the [Examples]
48/// directory for a more in depth example of the various configuration options and for how to handle
49/// state.
50///
51/// [Examples]: https://github.com/ratatui/ratatui/blob/master/examples/README.md
52///
53/// # Constructor methods
54///
55/// - [`Table::new`] creates a new [`Table`] with the given rows.
56/// - [`Table::default`] creates an empty [`Table`]. You can then add rows using [`Table::rows`].
57///
58/// # Setter methods
59///
60/// These methods are fluent setters. They return a new `Table` with the specified property set.
61///
62/// - [`Table::rows`] sets the rows of the [`Table`].
63/// - [`Table::header`] sets the header row of the [`Table`].
64/// - [`Table::footer`] sets the footer row of the [`Table`].
65/// - [`Table::widths`] sets the width constraints of each column.
66/// - [`Table::column_spacing`] sets the spacing between each column.
67/// - [`Table::block`] wraps the table in a [`Block`] widget.
68/// - [`Table::style`] sets the base style of the widget.
69/// - [`Table::row_highlight_style`] sets the style of the selected row.
70/// - [`Table::column_highlight_style`] sets the style of the selected column.
71/// - [`Table::cell_highlight_style`] sets the style of the selected cell.
72/// - [`Table::highlight_symbol`] sets the symbol to be displayed in front of the selected row.
73/// - [`Table::highlight_spacing`] sets when to show the highlight spacing.
74///
75/// # Example
76///
77/// ```rust
78/// use ratatui::layout::Constraint;
79/// use ratatui::style::{Style, Stylize};
80/// use ratatui::widgets::{Block, Row, Table};
81///
82/// let rows = [Row::new(vec!["Cell1", "Cell2", "Cell3"])];
83/// // Columns widths are constrained in the same way as Layout...
84/// let widths = [
85///     Constraint::Length(5),
86///     Constraint::Length(5),
87///     Constraint::Length(10),
88/// ];
89/// let table = Table::new(rows, widths)
90///     // ...and they can be separated by a fixed spacing.
91///     .column_spacing(1)
92///     // You can set the style of the entire Table.
93///     .style(Style::new().blue())
94///     // It has an optional header, which is simply a Row always visible at the top.
95///     .header(
96///         Row::new(vec!["Col1", "Col2", "Col3"])
97///             .style(Style::new().bold())
98///             // To add space between the header and the rest of the rows, specify the margin
99///             .bottom_margin(1),
100///     )
101///     // It has an optional footer, which is simply a Row always visible at the bottom.
102///     .footer(Row::new(vec!["Updated on Dec 28"]))
103///     // As any other widget, a Table can be wrapped in a Block.
104///     .block(Block::new().title("Table"))
105///     // The selected row, column, cell and its content can also be styled.
106///     .row_highlight_style(Style::new().reversed())
107///     .column_highlight_style(Style::new().red())
108///     .cell_highlight_style(Style::new().blue())
109///     // ...and potentially show a symbol in front of the selection.
110///     .highlight_symbol(">>");
111/// ```
112///
113/// Rows can be created from an iterator of [`Cell`]s. Each row can have an associated height,
114/// bottom margin, and style. See [`Row`] for more details.
115///
116/// ```rust
117/// use ratatui::style::{Style, Stylize};
118/// use ratatui::text::{Line, Span};
119/// use ratatui::widgets::{Cell, Row, Table};
120///
121/// // a Row can be created from simple strings.
122/// let row = Row::new(vec!["Row11", "Row12", "Row13"]);
123///
124/// // You can style the entire row.
125/// let row = Row::new(vec!["Row21", "Row22", "Row23"]).style(Style::new().red());
126///
127/// // If you need more control over the styling, create Cells directly
128/// let row = Row::new(vec![
129///     Cell::from("Row31"),
130///     Cell::from("Row32").style(Style::new().yellow()),
131///     Cell::from(Line::from(vec![Span::raw("Row"), Span::from("33").green()])),
132/// ]);
133///
134/// // If a Row need to display some content over multiple lines, specify the height.
135/// let row = Row::new(vec![
136///     Cell::from("Row\n41"),
137///     Cell::from("Row\n42"),
138///     Cell::from("Row\n43"),
139/// ])
140/// .height(2);
141/// ```
142///
143/// Cells can be created from anything that can be converted to [`Text`]. See [`Cell`] for more
144/// details.
145///
146/// ```rust
147/// use ratatui::style::{Style, Stylize};
148/// use ratatui::text::{Line, Span, Text};
149/// use ratatui::widgets::Cell;
150///
151/// Cell::from("simple string");
152/// Cell::from("simple styled span".red());
153/// Cell::from(Span::raw("raw span"));
154/// Cell::from(Span::styled("styled span", Style::new().red()));
155/// Cell::from(Line::from(vec![
156///     Span::raw("a vec of "),
157///     Span::from("spans").bold(),
158/// ]));
159/// Cell::from(Text::from("text"));
160/// ```
161///
162/// Just as rows can be collected from iterators of `Cell`s, tables can be collected from iterators
163/// of `Row`s.  This will create a table with column widths evenly dividing the space available.
164/// These default columns widths can be overridden using the `Table::widths` method.
165///
166/// ```rust
167/// use ratatui::layout::Constraint;
168/// use ratatui::widgets::{Row, Table};
169///
170/// let text = "Mary had a\nlittle lamb.";
171///
172/// let table = text
173///     .split("\n")
174///     .map(|line: &str| -> Row { line.split_ascii_whitespace().collect() })
175///     .collect::<Table>()
176///     .widths([Constraint::Length(10); 3]);
177/// ```
178///
179/// `Table` also implements the [`Styled`] trait, which means you can use style shorthands from
180/// the [`Stylize`] trait to set the style of the widget more concisely.
181///
182/// ```rust
183/// use ratatui::layout::Constraint;
184/// use ratatui::style::Stylize;
185/// use ratatui::widgets::{Row, Table};
186///
187/// let rows = [Row::new(vec!["Cell1", "Cell2", "Cell3"])];
188/// let widths = [
189///     Constraint::Length(5),
190///     Constraint::Length(5),
191///     Constraint::Length(10),
192/// ];
193/// let table = Table::new(rows, widths).red().italic();
194/// ```
195///
196/// # Stateful example
197///
198/// `Table` is a [`StatefulWidget`], which means you can use it with [`TableState`] to allow the
199/// user to scroll through the rows and select one of them.
200///
201/// ```rust
202/// use ratatui::Frame;
203/// use ratatui::layout::{Constraint, Rect};
204/// use ratatui::style::{Style, Stylize};
205/// use ratatui::widgets::{Block, Row, Table, TableState};
206///
207/// # fn ui(frame: &mut Frame) {
208/// # let area = Rect::default();
209/// // Note: TableState should be stored in your application state (not constructed in your render
210/// // method) so that the selected row is preserved across renders
211/// let mut table_state = TableState::default();
212/// let rows = [
213///     Row::new(vec!["Row11", "Row12", "Row13"]),
214///     Row::new(vec!["Row21", "Row22", "Row23"]),
215///     Row::new(vec!["Row31", "Row32", "Row33"]),
216/// ];
217/// let widths = [
218///     Constraint::Length(5),
219///     Constraint::Length(5),
220///     Constraint::Length(10),
221/// ];
222/// let table = Table::new(rows, widths)
223///     .block(Block::new().title("Table"))
224///     .row_highlight_style(Style::new().reversed())
225///     .highlight_symbol(">>");
226///
227/// frame.render_stateful_widget(table, area, &mut table_state);
228/// # }
229/// ```
230///
231/// [`Stylize`]: ratatui_core::style::Stylize
232#[derive(Debug, Clone, Eq, PartialEq, Hash)]
233pub struct Table<'a> {
234    /// Data to display in each row
235    rows: Vec<Row<'a>>,
236
237    /// Optional header
238    header: Option<Row<'a>>,
239
240    /// Optional footer
241    footer: Option<Row<'a>>,
242
243    /// Width constraints for each column
244    widths: Vec<Constraint>,
245
246    /// Space between each column
247    column_spacing: u16,
248
249    /// A block to wrap the widget in
250    block: Option<Block<'a>>,
251
252    /// Base style for the widget
253    style: Style,
254
255    /// Style used to render the selected row
256    row_highlight_style: Style,
257
258    /// Style used to render the selected column
259    column_highlight_style: Style,
260
261    /// Style used to render the selected cell
262    cell_highlight_style: Style,
263
264    /// Symbol in front of the selected row
265    highlight_symbol: Text<'a>,
266
267    /// Decides when to allocate spacing for the row selection
268    highlight_spacing: HighlightSpacing,
269
270    /// Controls how to distribute extra space among the columns
271    flex: Flex,
272}
273
274impl Default for Table<'_> {
275    fn default() -> Self {
276        Self {
277            rows: Vec::new(),
278            header: None,
279            footer: None,
280            widths: Vec::new(),
281            column_spacing: 1,
282            block: None,
283            style: Style::new(),
284            row_highlight_style: Style::new(),
285            column_highlight_style: Style::new(),
286            cell_highlight_style: Style::new(),
287            highlight_symbol: Text::default(),
288            highlight_spacing: HighlightSpacing::default(),
289            flex: Flex::Start,
290        }
291    }
292}
293
294impl<'a> Table<'a> {
295    /// Creates a new [`Table`] widget with the given rows.
296    ///
297    /// The `rows` parameter accepts any value that can be converted into an iterator of [`Row`]s.
298    /// This includes arrays, slices, and [`Vec`]s.
299    ///
300    /// The `widths` parameter accepts any type that implements `IntoIterator<Item =
301    /// Into<Constraint>>`. This includes arrays, slices, vectors, iterators. `Into<Constraint>` is
302    /// implemented on u16, so you can pass an array, vec, etc. of u16 to this function to create a
303    /// table with fixed width columns.
304    ///
305    /// # Examples
306    ///
307    /// ```rust
308    /// use ratatui::layout::Constraint;
309    /// use ratatui::widgets::{Row, Table};
310    ///
311    /// let rows = [
312    ///     Row::new(vec!["Cell1", "Cell2"]),
313    ///     Row::new(vec!["Cell3", "Cell4"]),
314    /// ];
315    /// let widths = [Constraint::Length(5), Constraint::Length(5)];
316    /// let table = Table::new(rows, widths);
317    /// ```
318    pub fn new<R, C>(rows: R, widths: C) -> Self
319    where
320        R: IntoIterator,
321        R::Item: Into<Row<'a>>,
322        C: IntoIterator,
323        C::Item: Into<Constraint>,
324    {
325        let widths = widths.into_iter().map(Into::into).collect_vec();
326        ensure_percentages_less_than_100(&widths);
327
328        let rows = rows.into_iter().map(Into::into).collect();
329        Self {
330            rows,
331            widths,
332            ..Default::default()
333        }
334    }
335
336    /// Set the rows
337    ///
338    /// The `rows` parameter accepts any value that can be converted into an iterator of [`Row`]s.
339    /// This includes arrays, slices, and [`Vec`]s.
340    ///
341    /// # Warning
342    ///
343    /// This method does not currently set the column widths. You will need to set them manually by
344    /// calling [`Table::widths`].
345    ///
346    /// This is a fluent setter method which must be chained or used as it consumes self
347    ///
348    /// # Examples
349    ///
350    /// ```rust
351    /// use ratatui::widgets::{Row, Table};
352    ///
353    /// let rows = [
354    ///     Row::new(vec!["Cell1", "Cell2"]),
355    ///     Row::new(vec!["Cell3", "Cell4"]),
356    /// ];
357    /// let table = Table::default().rows(rows);
358    /// ```
359    #[must_use = "method moves the value of self and returns the modified value"]
360    pub fn rows<T>(mut self, rows: T) -> Self
361    where
362        T: IntoIterator<Item = Row<'a>>,
363    {
364        self.rows = rows.into_iter().collect();
365        self
366    }
367
368    /// Sets the header row
369    ///
370    /// The `header` parameter is a [`Row`] which will be displayed at the top of the [`Table`]
371    ///
372    /// This is a fluent setter method which must be chained or used as it consumes self
373    ///
374    /// # Examples
375    ///
376    /// ```rust
377    /// use ratatui::widgets::{Cell, Row, Table};
378    ///
379    /// let header = Row::new(vec![
380    ///     Cell::from("Header Cell 1"),
381    ///     Cell::from("Header Cell 2"),
382    /// ]);
383    /// let table = Table::default().header(header);
384    /// ```
385    #[must_use = "method moves the value of self and returns the modified value"]
386    pub fn header(mut self, header: Row<'a>) -> Self {
387        self.header = Some(header);
388        self
389    }
390
391    /// Sets the footer row
392    ///
393    /// The `footer` parameter is a [`Row`] which will be displayed at the bottom of the [`Table`]
394    ///
395    /// This is a fluent setter method which must be chained or used as it consumes self
396    ///
397    /// # Examples
398    ///
399    /// ```rust
400    /// use ratatui::widgets::{Cell, Row, Table};
401    ///
402    /// let footer = Row::new(vec![
403    ///     Cell::from("Footer Cell 1"),
404    ///     Cell::from("Footer Cell 2"),
405    /// ]);
406    /// let table = Table::default().footer(footer);
407    /// ```
408    #[must_use = "method moves the value of self and returns the modified value"]
409    pub fn footer(mut self, footer: Row<'a>) -> Self {
410        self.footer = Some(footer);
411        self
412    }
413
414    /// Set the widths of the columns.
415    ///
416    /// The `widths` parameter accepts any type that implements `IntoIterator<Item =
417    /// Into<Constraint>>`. This includes arrays, slices, vectors, iterators. `Into<Constraint>` is
418    /// implemented on u16, so you can pass an array, vec, etc. of u16 to this function to create a
419    /// table with fixed width columns.
420    ///
421    /// If the widths are empty, the table will be rendered with equal widths.
422    ///
423    /// This is a fluent setter method which must be chained or used as it consumes self
424    ///
425    /// # Examples
426    ///
427    /// ```rust
428    /// use ratatui::layout::Constraint;
429    /// use ratatui::widgets::{Cell, Row, Table};
430    ///
431    /// let table = Table::default().widths([Constraint::Length(5), Constraint::Length(5)]);
432    /// let table = Table::default().widths(vec![Constraint::Length(5); 2]);
433    ///
434    /// // widths could also be computed at runtime
435    /// let widths = [10, 10, 20].into_iter().map(|c| Constraint::Length(c));
436    /// let table = Table::default().widths(widths);
437    /// ```
438    #[must_use = "method moves the value of self and returns the modified value"]
439    pub fn widths<I>(mut self, widths: I) -> Self
440    where
441        I: IntoIterator,
442        I::Item: Into<Constraint>,
443    {
444        let widths = widths.into_iter().map(Into::into).collect_vec();
445        ensure_percentages_less_than_100(&widths);
446        self.widths = widths;
447        self
448    }
449
450    /// Set the spacing between columns
451    ///
452    /// This is a fluent setter method which must be chained or used as it consumes self
453    ///
454    /// # Examples
455    ///
456    /// ```rust
457    /// use ratatui::layout::Constraint;
458    /// use ratatui::widgets::{Row, Table};
459    ///
460    /// let rows = [Row::new(vec!["Cell1", "Cell2"])];
461    /// let widths = [Constraint::Length(5), Constraint::Length(5)];
462    /// let table = Table::new(rows, widths).column_spacing(1);
463    /// ```
464    #[must_use = "method moves the value of self and returns the modified value"]
465    pub const fn column_spacing(mut self, spacing: u16) -> Self {
466        self.column_spacing = spacing;
467        self
468    }
469
470    /// Wraps the table with a custom [`Block`] widget.
471    ///
472    /// The `block` parameter is of type [`Block`]. This holds the specified block to be
473    /// created around the [`Table`]
474    ///
475    /// This is a fluent setter method which must be chained or used as it consumes self
476    ///
477    /// # Examples
478    ///
479    /// ```rust
480    /// use ratatui::layout::Constraint;
481    /// use ratatui::widgets::{Block, Cell, Row, Table};
482    ///
483    /// let rows = [Row::new(vec!["Cell1", "Cell2"])];
484    /// let widths = [Constraint::Length(5), Constraint::Length(5)];
485    /// let block = Block::bordered().title("Table");
486    /// let table = Table::new(rows, widths).block(block);
487    /// ```
488    #[must_use = "method moves the value of self and returns the modified value"]
489    pub fn block(mut self, block: Block<'a>) -> Self {
490        self.block = Some(block);
491        self
492    }
493
494    /// Sets the base style of the widget
495    ///
496    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
497    /// your own type that implements [`Into<Style>`]).
498    ///
499    /// All text rendered by the widget will use this style, unless overridden by [`Block::style`],
500    /// [`Row::style`], [`Cell::style`], or the styles of cell's content.
501    ///
502    /// This is a fluent setter method which must be chained or used as it consumes self
503    ///
504    /// # Examples
505    ///
506    /// ```rust
507    /// use ratatui::layout::Constraint;
508    /// use ratatui::style::{Style, Stylize};
509    /// use ratatui::widgets::{Row, Table};
510    ///
511    /// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
512    /// # let widths = [Constraint::Length(5), Constraint::Length(5)];
513    /// let table = Table::new(rows, widths).style(Style::new().red().italic());
514    /// ```
515    ///
516    /// `Table` also implements the [`Styled`] trait, which means you can use style shorthands from
517    /// the [`Stylize`] trait to set the style of the widget more concisely.
518    ///
519    /// ```rust
520    /// use ratatui::layout::Constraint;
521    /// use ratatui::style::Stylize;
522    /// use ratatui::widgets::{Cell, Row, Table};
523    ///
524    /// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
525    /// # let widths = vec![Constraint::Length(5), Constraint::Length(5)];
526    /// let table = Table::new(rows, widths).red().italic();
527    /// ```
528    ///
529    /// [`Color`]: ratatui_core::style::Color
530    /// [`Stylize`]: ratatui_core::style::Stylize
531    #[must_use = "method moves the value of self and returns the modified value"]
532    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
533        self.style = style.into();
534        self
535    }
536
537    /// Set the style of the selected row
538    ///
539    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
540    /// your own type that implements [`Into<Style>`]).
541    ///
542    /// This style will be applied to the entire row, including the selection symbol if it is
543    /// displayed, and will override any style set on the row or on the individual cells.
544    ///
545    /// This is a fluent setter method which must be chained or used as it consumes self
546    ///
547    /// # Examples
548    ///
549    /// ```rust
550    /// use ratatui::layout::Constraint;
551    /// use ratatui::style::{Style, Stylize};
552    /// use ratatui::widgets::{Cell, Row, Table};
553    ///
554    /// let rows = [Row::new(vec!["Cell1", "Cell2"])];
555    /// let widths = [Constraint::Length(5), Constraint::Length(5)];
556    /// let table = Table::new(rows, widths).highlight_style(Style::new().red().italic());
557    /// ```
558    ///
559    /// [`Color`]: ratatui_core::style::Color
560    #[must_use = "method moves the value of self and returns the modified value"]
561    #[deprecated(note = "use `row_highlight_style()` instead")]
562    pub fn highlight_style<S: Into<Style>>(self, highlight_style: S) -> Self {
563        self.row_highlight_style(highlight_style)
564    }
565
566    /// Set the style of the selected row
567    ///
568    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
569    /// your own type that implements [`Into<Style>`]).
570    ///
571    /// This style will be applied to the entire row, including the selection symbol if it is
572    /// displayed, and will override any style set on the row or on the individual cells.
573    ///
574    /// This is a fluent setter method which must be chained or used as it consumes self
575    ///
576    /// # Examples
577    ///
578    /// ```rust
579    /// # use ratatui::{layout::Constraint, style::{Style, Stylize}, widgets::{Row, Table}};
580    /// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
581    /// # let widths = [Constraint::Length(5), Constraint::Length(5)];
582    /// let table = Table::new(rows, widths).row_highlight_style(Style::new().red().italic());
583    /// ```
584    /// [`Color`]: ratatui_core::style::Color
585    #[must_use = "method moves the value of self and returns the modified value"]
586    pub fn row_highlight_style<S: Into<Style>>(mut self, highlight_style: S) -> Self {
587        self.row_highlight_style = highlight_style.into();
588        self
589    }
590
591    /// Set the style of the selected column
592    ///
593    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
594    /// your own type that implements [`Into<Style>`]).
595    ///
596    /// This style will be applied to the entire column, and will override any style set on the
597    /// row or on the individual cells.
598    ///
599    /// This is a fluent setter method which must be chained or used as it consumes self
600    ///
601    /// # Examples
602    ///
603    /// ```rust
604    /// # use ratatui::{layout::Constraint, style::{Style, Stylize}, widgets::{Row, Table}};
605    /// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
606    /// # let widths = [Constraint::Length(5), Constraint::Length(5)];
607    /// let table = Table::new(rows, widths).column_highlight_style(Style::new().red().italic());
608    /// ```
609    /// [`Color`]: ratatui_core::style::Color
610    #[must_use = "method moves the value of self and returns the modified value"]
611    pub fn column_highlight_style<S: Into<Style>>(mut self, highlight_style: S) -> Self {
612        self.column_highlight_style = highlight_style.into();
613        self
614    }
615
616    /// Set the style of the selected cell
617    ///
618    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
619    /// your own type that implements [`Into<Style>`]).
620    ///
621    /// This style will be applied to the selected cell, and will override any style set on the
622    /// row or on the individual cells.
623    ///
624    /// This is a fluent setter method which must be chained or used as it consumes self
625    ///
626    /// # Examples
627    ///
628    /// ```rust
629    /// # use ratatui::{layout::Constraint, style::{Style, Stylize}, widgets::{Row, Table}};
630    /// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
631    /// # let widths = [Constraint::Length(5), Constraint::Length(5)];
632    /// let table = Table::new(rows, widths).cell_highlight_style(Style::new().red().italic());
633    /// ```
634    /// [`Color`]: ratatui_core::style::Color
635    #[must_use = "method moves the value of self and returns the modified value"]
636    pub fn cell_highlight_style<S: Into<Style>>(mut self, highlight_style: S) -> Self {
637        self.cell_highlight_style = highlight_style.into();
638        self
639    }
640
641    /// Set the symbol to be displayed in front of the selected row
642    ///
643    /// This is a fluent setter method which must be chained or used as it consumes self
644    ///
645    /// # Examples
646    ///
647    /// ```rust
648    /// use ratatui::layout::Constraint;
649    /// use ratatui::widgets::{Cell, Row, Table};
650    ///
651    /// # let rows = [Row::new(vec!["Cell1", "Cell2"])];
652    /// # let widths = [Constraint::Length(5), Constraint::Length(5)];
653    /// let table = Table::new(rows, widths).highlight_symbol(">>");
654    /// ```
655    #[must_use = "method moves the value of self and returns the modified value"]
656    pub fn highlight_symbol<T: Into<Text<'a>>>(mut self, highlight_symbol: T) -> Self {
657        self.highlight_symbol = highlight_symbol.into();
658        self
659    }
660
661    /// Set when to show the highlight spacing
662    ///
663    /// The highlight spacing is the spacing that is allocated for the selection symbol column (if
664    /// enabled) and is used to shift the table when a row is selected. This method allows you to
665    /// configure when this spacing is allocated.
666    ///
667    /// - [`HighlightSpacing::Always`] will always allocate the spacing, regardless of whether a row
668    ///   is selected or not. This means that the table will never change size, regardless of if a
669    ///   row is selected or not.
670    /// - [`HighlightSpacing::WhenSelected`] will only allocate the spacing if a row is selected.
671    ///   This means that the table will shift when a row is selected. This is the default setting
672    ///   for backwards compatibility, but it is recommended to use `HighlightSpacing::Always` for a
673    ///   better user experience.
674    /// - [`HighlightSpacing::Never`] will never allocate the spacing, regardless of whether a row
675    ///   is selected or not. This means that the highlight symbol will never be drawn.
676    ///
677    /// This is a fluent setter method which must be chained or used as it consumes self
678    ///
679    /// # Examples
680    ///
681    /// ```rust
682    /// use ratatui::layout::Constraint;
683    /// use ratatui::widgets::{HighlightSpacing, Row, Table};
684    ///
685    /// let rows = [Row::new(vec!["Cell1", "Cell2"])];
686    /// let widths = [Constraint::Length(5), Constraint::Length(5)];
687    /// let table = Table::new(rows, widths).highlight_spacing(HighlightSpacing::Always);
688    /// ```
689    #[must_use = "method moves the value of self and returns the modified value"]
690    pub const fn highlight_spacing(mut self, value: HighlightSpacing) -> Self {
691        self.highlight_spacing = value;
692        self
693    }
694
695    /// Set how extra space is distributed amongst columns.
696    ///
697    /// This determines how the space is distributed when the constraints are satisfied. By default,
698    /// the extra space is not distributed at all.  But this can be changed to distribute all extra
699    /// space to the last column or to distribute it equally.
700    ///
701    /// This is a fluent setter method which must be chained or used as it consumes self
702    ///
703    /// # Examples
704    ///
705    /// Create a table that needs at least 30 columns to display.  Any extra space will be assigned
706    /// to the last column.
707    /// ```
708    /// use ratatui::layout::{Constraint, Flex};
709    /// use ratatui::widgets::{Row, Table};
710    ///
711    /// let widths = [
712    ///     Constraint::Min(10),
713    ///     Constraint::Min(10),
714    ///     Constraint::Min(10),
715    /// ];
716    /// let table = Table::new(Vec::<Row>::new(), widths).flex(Flex::Legacy);
717    /// ```
718    #[must_use = "method moves the value of self and returns the modified value"]
719    pub const fn flex(mut self, flex: Flex) -> Self {
720        self.flex = flex;
721        self
722    }
723}
724
725impl Widget for Table<'_> {
726    fn render(self, area: Rect, buf: &mut Buffer) {
727        Widget::render(&self, area, buf);
728    }
729}
730
731impl Widget for &Table<'_> {
732    fn render(self, area: Rect, buf: &mut Buffer) {
733        let mut state = TableState::default();
734        StatefulWidget::render(self, area, buf, &mut state);
735    }
736}
737
738impl StatefulWidget for Table<'_> {
739    type State = TableState;
740
741    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
742        StatefulWidget::render(&self, area, buf, state);
743    }
744}
745
746impl StatefulWidget for &Table<'_> {
747    type State = TableState;
748
749    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
750        buf.set_style(area, self.style);
751        self.block.as_ref().render(area, buf);
752        let table_area = self.block.inner_if_some(area);
753        if table_area.is_empty() {
754            return;
755        }
756
757        if state.selected.is_some_and(|s| s >= self.rows.len()) {
758            state.select(Some(self.rows.len().saturating_sub(1)));
759        }
760
761        if self.rows.is_empty() {
762            state.select(None);
763        }
764
765        let column_count = self.column_count();
766        if state.selected_column.is_some_and(|s| s >= column_count) {
767            state.select_column(Some(column_count.saturating_sub(1)));
768        }
769        if column_count == 0 {
770            state.select_column(None);
771        }
772
773        let selection_width = self.selection_width(state);
774        let column_widths = self.get_column_widths(table_area.width, selection_width, column_count);
775        let (header_area, rows_area, footer_area) = self.layout(table_area);
776
777        self.render_header(header_area, buf, &column_widths);
778
779        self.render_rows(rows_area, buf, selection_width, state, &column_widths);
780
781        self.render_footer(footer_area, buf, &column_widths);
782    }
783}
784
785// private methods for rendering
786impl Table<'_> {
787    /// Splits the table area into a header, rows area and a footer
788    fn layout(&self, area: Rect) -> (Rect, Rect, Rect) {
789        let header_top_margin = self.header.as_ref().map_or(0, |h| h.top_margin);
790        let header_height = self.header.as_ref().map_or(0, |h| h.height);
791        let header_bottom_margin = self.header.as_ref().map_or(0, |h| h.bottom_margin);
792        let footer_top_margin = self.footer.as_ref().map_or(0, |h| h.top_margin);
793        let footer_height = self.footer.as_ref().map_or(0, |f| f.height);
794        let footer_bottom_margin = self.footer.as_ref().map_or(0, |h| h.bottom_margin);
795        let layout = Layout::vertical([
796            Constraint::Length(header_top_margin),
797            Constraint::Length(header_height),
798            Constraint::Length(header_bottom_margin),
799            Constraint::Min(0),
800            Constraint::Length(footer_top_margin),
801            Constraint::Length(footer_height),
802            Constraint::Length(footer_bottom_margin),
803        ])
804        .split(area);
805        let (header_area, rows_area, footer_area) = (layout[1], layout[3], layout[5]);
806        (header_area, rows_area, footer_area)
807    }
808
809    /// Render the header cells, if they are not `None`
810    ///
811    /// The `x` and `width` fields of each `Rect` in `column_widths` denote the starting
812    /// x-coordinate and width of each column in the table.
813    fn render_header(&self, area: Rect, buf: &mut Buffer, column_widths: &[Rect]) {
814        if let Some(ref header) = self.header {
815            buf.set_style(area, header.style);
816            for (cell_area, cell) in column_widths.iter().zip(header.cells.iter()) {
817                let new_x = area.x + cell_area.x;
818                let area_to_render = Rect::new(new_x, area.y, cell_area.width, area.height);
819                cell.render(area_to_render, buf);
820            }
821        }
822    }
823
824    /// Render the footer cells, if they are not `None`
825    ///
826    /// The `x` and `width` fields of each `Rect` in `column_widths` denote the starting
827    /// x-coordinate and width of each column in the table.
828    fn render_footer(&self, area: Rect, buf: &mut Buffer, column_widths: &[Rect]) {
829        if let Some(ref footer) = self.footer {
830            buf.set_style(area, footer.style);
831            for (cell_area, cell) in column_widths.iter().zip(footer.cells.iter()) {
832                let new_x = area.x + cell_area.x;
833                let area_to_render = Rect::new(new_x, area.y, cell_area.width, area.height);
834                cell.render(area_to_render, buf);
835            }
836        }
837    }
838
839    /// Render the table rows
840    ///
841    /// The `x` and `width` fields of each `Rect` in `column_widths` denote the starting
842    /// x-coordinate and width of each column in the table.
843    fn render_rows(
844        &self,
845        area: Rect,
846        buf: &mut Buffer,
847        selection_width: u16,
848        state: &mut TableState,
849        columns_widths: &[Rect],
850    ) {
851        if self.rows.is_empty() {
852            return;
853        }
854
855        let (start_index, end_index) = self.visible_rows(state, area);
856        state.offset = start_index;
857
858        let mut y_offset = 0;
859
860        let mut selected_row_area = None;
861        for (i, row) in self
862            .rows
863            .iter()
864            .enumerate()
865            .skip(start_index)
866            .take(end_index - start_index)
867        {
868            let y = area.y + y_offset + row.top_margin;
869            let height = (y + row.height).min(area.bottom()).saturating_sub(y);
870            let row_area = Rect { y, height, ..area };
871            buf.set_style(row_area, row.style);
872
873            let is_selected = state.selected.is_some_and(|index| index == i);
874            if selection_width > 0 && is_selected {
875                self.set_selection_style(buf, selection_width, row_area, row);
876            }
877            self.render_row_cells(buf, columns_widths.iter().collect(), &row.cells, row_area);
878            if is_selected {
879                selected_row_area = Some(row_area);
880            }
881            y_offset += row.height_with_margin();
882        }
883
884        let selected_column_area = state.selected_column.and_then(|s| {
885            // The selection is clamped by the column count. Since a user can manually specify an
886            // incorrect number of widths, we should use panic free methods.
887            columns_widths.get(s).map(|cell_area| Rect {
888                x: cell_area.x + area.x,
889                width: cell_area.width,
890                ..area
891            })
892        });
893
894        match (selected_row_area, selected_column_area) {
895            (Some(row_area), Some(col_area)) => {
896                buf.set_style(row_area, self.row_highlight_style);
897                buf.set_style(col_area, self.column_highlight_style);
898                let cell_area = row_area.intersection(col_area);
899                buf.set_style(cell_area, self.cell_highlight_style);
900            }
901            (Some(row_area), None) => {
902                buf.set_style(row_area, self.row_highlight_style);
903            }
904            (None, Some(col_area)) => {
905                buf.set_style(col_area, self.column_highlight_style);
906            }
907            (None, None) => (),
908        }
909    }
910
911    /// Render cells into the columns of a row
912    ///
913    /// Render `Cell`s from `cells` into columns specified by `column_widths`, stopping
914    /// if either of these iterators are finished.  Each `Cell` gets rendered across
915    /// [`Cell::get_column_span`] columns plus the gaps between them, if this value is > 1.
916    fn render_row_cells(
917        &self,
918        buf: &mut Buffer,
919        column_widths: Vec<&Rect>,
920        cells: &Vec<Cell>,
921        row_area: Rect,
922    ) {
923        let mut column_widths_iterator = column_widths.into_iter();
924        for current_cell in cells {
925            if let Some(cell_area) = Self::get_cell_area(
926                &mut column_widths_iterator,
927                current_cell.column_span,
928                self.column_spacing,
929            ) {
930                let new_x = row_area.x + cell_area.x;
931                let area_to_render = Rect::new(new_x, row_area.y, cell_area.width, row_area.height);
932                current_cell.render(area_to_render, buf);
933            }
934        }
935    }
936
937    /// Set the row style and render the highlight symbol
938    fn set_selection_style(
939        &self,
940        buf: &mut Buffer,
941        selection_width: u16,
942        row_area: Rect,
943        row: &Row,
944    ) {
945        let selection_area = Rect {
946            width: selection_width,
947            ..row_area
948        };
949        buf.set_style(selection_area, row.style);
950        (&self.highlight_symbol).render(selection_area, buf);
951    }
952
953    /// Return the area that a [`Cell`] should occupy, taking into account its
954    /// [`Cell::column_span`].
955    ///
956    /// Returns `None` when there are no more columns for the [`Cell`] to occupy.
957    ///
958    /// Otherwise, returns `Some(Rect{x, y = 0, width, height = 0})`, representing the start
959    /// x-coordinate and width of the [`Cell`].
960    ///
961    /// This function consumes `cell_column_span` `Rect`s from `column_widths_iterator` (or all the
962    /// `Rects` if the iterator is less than `cell_column_span` `Rect`s long). This function adds
963    /// the width of each `Rect` plus `column_spacing` to a running total of the final width.  The
964    /// return value is the original x coordinate and the final width, or `None` if
965    /// `column_widths_iterator` is empty or `cell_column_span` is `0`.
966    fn get_cell_area<'a, T>(
967        column_widths_iterator: &mut T,
968        cell_column_span: u16,
969        column_spacing: u16,
970    ) -> Option<Rect>
971    where
972        T: Iterator<Item = &'a Rect>,
973    {
974        if cell_column_span == 0 {
975            return None;
976        }
977        let first = column_widths_iterator.next()?;
978        let (n_columns_taken, all_columns_width) = column_widths_iterator
979            .take((cell_column_span - 1).into())
980            .map(|rect| (1, rect.width))
981            .fold((1, first.width), |so_far, next_column| {
982                (next_column.0 + so_far.0, next_column.1 + so_far.1)
983            });
984        let width = all_columns_width + (n_columns_taken - 1) * column_spacing;
985        Some(Rect::new(first.x, first.y, width, 1))
986    }
987
988    /// Return the indexes of the visible rows.
989    ///
990    /// The algorithm works as follows:
991    /// - start at the offset and calculate the height of the rows that can be displayed within the
992    ///   area.
993    /// - if the selected row is not visible, scroll the table to ensure it is visible.
994    /// - if there is still space to fill then there's a partial row at the end which should be
995    ///   included in the view.
996    fn visible_rows(&self, state: &TableState, area: Rect) -> (usize, usize) {
997        let last_row = self.rows.len().saturating_sub(1);
998        let mut start = state.offset.min(last_row);
999
1000        if let Some(selected) = state.selected {
1001            start = start.min(selected);
1002        }
1003
1004        let mut end = start;
1005        let mut height = 0;
1006
1007        for item in self.rows.iter().skip(start) {
1008            if height + item.height > area.height {
1009                break;
1010            }
1011            height += item.height_with_margin();
1012            end += 1;
1013        }
1014
1015        if let Some(selected) = state.selected {
1016            let selected = selected.min(last_row);
1017
1018            // scroll down until the selected row is visible
1019            while selected >= end {
1020                height = height.saturating_add(self.rows[end].height_with_margin());
1021                end += 1;
1022                while height > area.height {
1023                    height = height.saturating_sub(self.rows[start].height_with_margin());
1024                    start += 1;
1025                }
1026            }
1027        }
1028
1029        // Include a partial row if there is space
1030        if height < area.height && end < self.rows.len() {
1031            end += 1;
1032        }
1033
1034        (start, end)
1035    }
1036
1037    /// Get all offsets and widths of all user specified columns.
1038    ///
1039    /// Returns (x, width). When self.widths is empty, it is assumed `.widths()` has not been called
1040    /// and a default of equal widths is returned.
1041    fn get_column_widths(
1042        &self,
1043        max_width: u16,
1044        selection_width: u16,
1045        col_count: usize,
1046    ) -> Vec<Rect> {
1047        let widths = if self.widths.is_empty() {
1048            // Divide the space between each column equally
1049            vec![Constraint::Length(max_width / col_count.max(1) as u16); col_count]
1050        } else {
1051            self.widths.clone()
1052        };
1053        // this will always allocate a selection area
1054        let [_selection_area, columns_area] =
1055            Layout::horizontal([Constraint::Length(selection_width), Constraint::Fill(0)])
1056                .areas(Rect::new(0, 0, max_width, 1));
1057        let rects = Layout::horizontal(widths)
1058            .flex(self.flex)
1059            .spacing(self.column_spacing)
1060            .split(columns_area);
1061        rects
1062            .iter()
1063            .map(|c| Rect::new(c.x, 0, c.width, 1))
1064            .collect()
1065    }
1066
1067    fn column_count(&self) -> usize {
1068        self.rows
1069            .iter()
1070            .chain(self.footer.iter())
1071            .chain(self.header.iter())
1072            .map(|r| r.cells.len())
1073            .max()
1074            .unwrap_or_default()
1075    }
1076
1077    /// Returns the width of the selection column if a row is selected, or the `highlight_spacing`
1078    /// is set to show the column always, otherwise 0.
1079    fn selection_width(&self, state: &TableState) -> u16 {
1080        let has_selection = state.selected.is_some();
1081        if self.highlight_spacing.should_add(has_selection) {
1082            self.highlight_symbol.width() as u16
1083        } else {
1084            0
1085        }
1086    }
1087}
1088
1089fn ensure_percentages_less_than_100(widths: &[Constraint]) {
1090    for w in widths {
1091        if let Constraint::Percentage(p) = w {
1092            assert!(
1093                *p <= 100,
1094                "Percentages should be between 0 and 100 inclusively."
1095            );
1096        }
1097    }
1098}
1099
1100impl Styled for Table<'_> {
1101    type Item = Self;
1102
1103    fn style(&self) -> Style {
1104        self.style
1105    }
1106
1107    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
1108        self.style(style)
1109    }
1110}
1111
1112impl<'a, Item> FromIterator<Item> for Table<'a>
1113where
1114    Item: Into<Row<'a>>,
1115{
1116    /// Collects an iterator of rows into a table.
1117    ///
1118    /// When collecting from an iterator into a table, the user must provide the widths using
1119    /// `Table::widths` after construction.
1120    fn from_iter<Iter: IntoIterator<Item = Item>>(rows: Iter) -> Self {
1121        let widths: [Constraint; 0] = [];
1122        Self::new(rows, widths)
1123    }
1124}
1125
1126#[cfg(test)]
1127mod tests {
1128    use alloc::string::ToString;
1129    use alloc::{format, vec};
1130
1131    use ratatui_core::layout::Constraint::*;
1132    use ratatui_core::style::{Color, Modifier, Style, Stylize};
1133    use ratatui_core::text::Line;
1134    use rstest::{fixture, rstest};
1135
1136    use super::*;
1137    use crate::table::Cell;
1138
1139    #[test]
1140    fn new() {
1141        let rows = [Row::new(vec![Cell::from("")])];
1142        let widths = [Constraint::Percentage(100)];
1143        let table = Table::new(rows.clone(), widths);
1144        assert_eq!(table.rows, rows);
1145        assert_eq!(table.header, None);
1146        assert_eq!(table.footer, None);
1147        assert_eq!(table.widths, widths);
1148        assert_eq!(table.column_spacing, 1);
1149        assert_eq!(table.block, None);
1150        assert_eq!(table.style, Style::default());
1151        assert_eq!(table.row_highlight_style, Style::default());
1152        assert_eq!(table.highlight_symbol, Text::default());
1153        assert_eq!(table.highlight_spacing, HighlightSpacing::WhenSelected);
1154        assert_eq!(table.flex, Flex::Start);
1155    }
1156
1157    #[test]
1158    fn default() {
1159        let table = Table::default();
1160        assert_eq!(table.rows, []);
1161        assert_eq!(table.header, None);
1162        assert_eq!(table.footer, None);
1163        assert_eq!(table.widths, []);
1164        assert_eq!(table.column_spacing, 1);
1165        assert_eq!(table.block, None);
1166        assert_eq!(table.style, Style::default());
1167        assert_eq!(table.row_highlight_style, Style::default());
1168        assert_eq!(table.highlight_symbol, Text::default());
1169        assert_eq!(table.highlight_spacing, HighlightSpacing::WhenSelected);
1170        assert_eq!(table.flex, Flex::Start);
1171    }
1172
1173    #[test]
1174    fn collect() {
1175        let table = (0..4)
1176            .map(|i| -> Row { (0..4).map(|j| format!("{i}*{j} = {}", i * j)).collect() })
1177            .collect::<Table>()
1178            .widths([Constraint::Percentage(25); 4]);
1179
1180        let expected_rows: Vec<Row> = vec![
1181            Row::new(["0*0 = 0", "0*1 = 0", "0*2 = 0", "0*3 = 0"]),
1182            Row::new(["1*0 = 0", "1*1 = 1", "1*2 = 2", "1*3 = 3"]),
1183            Row::new(["2*0 = 0", "2*1 = 2", "2*2 = 4", "2*3 = 6"]),
1184            Row::new(["3*0 = 0", "3*1 = 3", "3*2 = 6", "3*3 = 9"]),
1185        ];
1186
1187        assert_eq!(table.rows, expected_rows);
1188        assert_eq!(table.widths, [Constraint::Percentage(25); 4]);
1189    }
1190
1191    #[test]
1192    fn widths() {
1193        let table = Table::default().widths([Constraint::Length(100)]);
1194        assert_eq!(table.widths, [Constraint::Length(100)]);
1195
1196        // ensure that code that uses &[] continues to work as there is a large amount of code that
1197        // uses this pattern
1198        #[expect(clippy::needless_borrows_for_generic_args)]
1199        let table = Table::default().widths(&[Constraint::Length(100)]);
1200        assert_eq!(table.widths, [Constraint::Length(100)]);
1201
1202        let table = Table::default().widths(vec![Constraint::Length(100)]);
1203        assert_eq!(table.widths, [Constraint::Length(100)]);
1204
1205        // ensure that code that uses &some_vec continues to work as there is a large amount of code
1206        // that uses this pattern
1207        #[expect(clippy::needless_borrows_for_generic_args)]
1208        let table = Table::default().widths(&vec![Constraint::Length(100)]);
1209        assert_eq!(table.widths, [Constraint::Length(100)]);
1210
1211        let table = Table::default().widths([100].into_iter().map(Constraint::Length));
1212        assert_eq!(table.widths, [Constraint::Length(100)]);
1213    }
1214
1215    #[test]
1216    fn rows() {
1217        let rows = [Row::new(vec![Cell::from("")])];
1218        let table = Table::default().rows(rows.clone());
1219        assert_eq!(table.rows, rows);
1220    }
1221
1222    #[test]
1223    fn column_spacing() {
1224        let table = Table::default().column_spacing(2);
1225        assert_eq!(table.column_spacing, 2);
1226    }
1227
1228    #[test]
1229    fn block() {
1230        let block = Block::bordered().title("Table");
1231        let table = Table::default().block(block.clone());
1232        assert_eq!(table.block, Some(block));
1233    }
1234
1235    #[test]
1236    fn header() {
1237        let header = Row::new(vec![Cell::from("")]);
1238        let table = Table::default().header(header.clone());
1239        assert_eq!(table.header, Some(header));
1240    }
1241
1242    #[test]
1243    fn footer() {
1244        let footer = Row::new(vec![Cell::from("")]);
1245        let table = Table::default().footer(footer.clone());
1246        assert_eq!(table.footer, Some(footer));
1247    }
1248
1249    #[test]
1250    #[expect(deprecated)]
1251    fn highlight_style() {
1252        let style = Style::default().red().italic();
1253        let table = Table::default().highlight_style(style);
1254        assert_eq!(table.row_highlight_style, style);
1255    }
1256
1257    #[test]
1258    fn row_highlight_style() {
1259        let style = Style::default().red().italic();
1260        let table = Table::default().row_highlight_style(style);
1261        assert_eq!(table.row_highlight_style, style);
1262    }
1263
1264    #[test]
1265    fn column_highlight_style() {
1266        let style = Style::default().red().italic();
1267        let table = Table::default().column_highlight_style(style);
1268        assert_eq!(table.column_highlight_style, style);
1269    }
1270
1271    #[test]
1272    fn cell_highlight_style() {
1273        let style = Style::default().red().italic();
1274        let table = Table::default().cell_highlight_style(style);
1275        assert_eq!(table.cell_highlight_style, style);
1276    }
1277
1278    #[test]
1279    fn highlight_symbol() {
1280        let table = Table::default().highlight_symbol(">>");
1281        assert_eq!(table.highlight_symbol, Text::from(">>"));
1282    }
1283
1284    #[test]
1285    fn highlight_spacing() {
1286        let table = Table::default().highlight_spacing(HighlightSpacing::Always);
1287        assert_eq!(table.highlight_spacing, HighlightSpacing::Always);
1288    }
1289
1290    #[test]
1291    #[should_panic = "Percentages should be between 0 and 100 inclusively"]
1292    fn table_invalid_percentages() {
1293        let _ = Table::default().widths([Constraint::Percentage(110)]);
1294    }
1295
1296    #[test]
1297    fn widths_conversions() {
1298        let array = [Constraint::Percentage(100)];
1299        let table = Table::new(Vec::<Row>::new(), array);
1300        assert_eq!(table.widths, [Constraint::Percentage(100)], "array");
1301
1302        let array_ref = &[Constraint::Percentage(100)];
1303        let table = Table::new(Vec::<Row>::new(), array_ref);
1304        assert_eq!(table.widths, [Constraint::Percentage(100)], "array ref");
1305
1306        let vec = vec![Constraint::Percentage(100)];
1307        let slice = vec.as_slice();
1308        let table = Table::new(Vec::<Row>::new(), slice);
1309        assert_eq!(table.widths, [Constraint::Percentage(100)], "slice");
1310
1311        let vec = vec![Constraint::Percentage(100)];
1312        let table = Table::new(Vec::<Row>::new(), vec);
1313        assert_eq!(table.widths, [Constraint::Percentage(100)], "vec");
1314
1315        let vec_ref = &vec![Constraint::Percentage(100)];
1316        let table = Table::new(Vec::<Row>::new(), vec_ref);
1317        assert_eq!(table.widths, [Constraint::Percentage(100)], "vec ref");
1318    }
1319
1320    #[cfg(test)]
1321    mod state {
1322        use ratatui_core::buffer::Buffer;
1323        use ratatui_core::layout::{Constraint, Rect};
1324        use ratatui_core::widgets::StatefulWidget;
1325
1326        use super::*;
1327        use crate::table::{Row, Table, TableState};
1328
1329        #[fixture]
1330        fn table_buf() -> Buffer {
1331            Buffer::empty(Rect::new(0, 0, 10, 10))
1332        }
1333
1334        #[rstest]
1335        fn test_list_state_empty_list(mut table_buf: Buffer) {
1336            let mut state = TableState::default();
1337
1338            let rows: Vec<Row> = Vec::new();
1339            let widths = vec![Constraint::Percentage(100)];
1340            let table = Table::new(rows, widths);
1341            state.select_first();
1342            StatefulWidget::render(table, table_buf.area, &mut table_buf, &mut state);
1343            assert_eq!(state.selected, None);
1344            assert_eq!(state.selected_column, None);
1345        }
1346
1347        #[rstest]
1348        fn test_list_state_single_item(mut table_buf: Buffer) {
1349            let mut state = TableState::default();
1350
1351            let widths = vec![Constraint::Percentage(100)];
1352
1353            let items = vec![Row::new(vec!["Item 1"])];
1354            let table = Table::new(items, widths);
1355            state.select_first();
1356            StatefulWidget::render(&table, table_buf.area, &mut table_buf, &mut state);
1357            assert_eq!(state.selected, Some(0));
1358            assert_eq!(state.selected_column, None);
1359
1360            state.select_last();
1361            StatefulWidget::render(&table, table_buf.area, &mut table_buf, &mut state);
1362            assert_eq!(state.selected, Some(0));
1363            assert_eq!(state.selected_column, None);
1364
1365            state.select_previous();
1366            StatefulWidget::render(&table, table_buf.area, &mut table_buf, &mut state);
1367            assert_eq!(state.selected, Some(0));
1368            assert_eq!(state.selected_column, None);
1369
1370            state.select_next();
1371            StatefulWidget::render(&table, table_buf.area, &mut table_buf, &mut state);
1372            assert_eq!(state.selected, Some(0));
1373            assert_eq!(state.selected_column, None);
1374
1375            let mut state = TableState::default();
1376
1377            state.select_first_column();
1378            StatefulWidget::render(&table, table_buf.area, &mut table_buf, &mut state);
1379            assert_eq!(state.selected_column, Some(0));
1380            assert_eq!(state.selected, None);
1381
1382            state.select_last_column();
1383            StatefulWidget::render(&table, table_buf.area, &mut table_buf, &mut state);
1384            assert_eq!(state.selected_column, Some(0));
1385            assert_eq!(state.selected, None);
1386
1387            state.select_previous_column();
1388            StatefulWidget::render(&table, table_buf.area, &mut table_buf, &mut state);
1389            assert_eq!(state.selected_column, Some(0));
1390            assert_eq!(state.selected, None);
1391
1392            state.select_next_column();
1393            StatefulWidget::render(&table, table_buf.area, &mut table_buf, &mut state);
1394            assert_eq!(state.selected_column, Some(0));
1395            assert_eq!(state.selected, None);
1396        }
1397    }
1398
1399    #[cfg(test)]
1400    mod render {
1401        use ratatui_core::layout::Alignment;
1402
1403        use super::*;
1404
1405        #[test]
1406        fn render_empty_area() {
1407            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1408            let rows = vec![Row::new(vec!["Cell1", "Cell2"])];
1409            let table = Table::new(rows, vec![Constraint::Length(5); 2]);
1410            Widget::render(table, Rect::new(0, 0, 0, 0), &mut buf);
1411            assert_eq!(buf, Buffer::empty(Rect::new(0, 0, 15, 3)));
1412        }
1413
1414        #[test]
1415        fn render_default() {
1416            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1417            let table = Table::default();
1418            Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
1419            assert_eq!(buf, Buffer::empty(Rect::new(0, 0, 15, 3)));
1420        }
1421
1422        #[test]
1423        fn render_with_block() {
1424            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1425            let rows = vec![
1426                Row::new(vec!["Cell1", "Cell2"]),
1427                Row::new(vec!["Cell3", "Cell4"]),
1428            ];
1429            let block = Block::bordered().title("Block");
1430            let table = Table::new(rows, vec![Constraint::Length(5); 2]).block(block);
1431            Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
1432            #[rustfmt::skip]
1433            let expected = Buffer::with_lines([
1434                "┌Block────────┐",
1435                "│Cell1 Cell2  │",
1436                "└─────────────┘",
1437            ]);
1438            assert_eq!(buf, expected);
1439        }
1440
1441        #[rstest]
1442        #[case(15, 5, vec![
1443                Row::new(vec![
1444                    Cell::new("Cell1").column_span(1),
1445                    Cell::new("Cell2").column_span(1),
1446                ]),
1447                Row::new(vec![
1448                    Cell::new("Cell3").column_span(1),
1449                    Cell::new("Cell4").column_span(1),
1450                ]),
1451            ],
1452            &Buffer::with_lines(["Cell1 Cell2    ", "Cell3 Cell4    "]))]
1453        #[case(15, 5, vec![
1454                Row::new(vec![
1455                    Cell::new("Cell1").column_span(0),
1456                    Cell::new("Cell2").column_span(1),
1457                ]),
1458                Row::new(vec![
1459                    Cell::new("Cell3").column_span(1),
1460                    Cell::new("Cell4").column_span(1),
1461                ]),
1462            ], &Buffer::with_lines(["Cell2          ", "Cell3 Cell4    "]))]
1463        #[case(15, 5, vec![
1464                Row::new(vec![
1465                    Cell::new("Cell1").column_span(2),
1466                    Cell::new("Cell2").column_span(1),
1467                ]),
1468                Row::new(vec![
1469                    Cell::new("Cell3").column_span(1),
1470                    Cell::new("Cell4").column_span(1),
1471                ]),
1472            ], &Buffer::with_lines(["Cell1          ", "Cell3 Cell4    "]))]
1473        fn test_colspans_2_cols<'rows, Rows>(
1474            #[case] width: u16,
1475            #[case] column_width: u16,
1476            #[case] rows: Rows,
1477            #[case] expected: &Buffer,
1478        ) where
1479            Rows: IntoIterator<Item = Row<'rows>>,
1480        {
1481            let mut buf = Buffer::empty(Rect::new(0, 0, width, 2));
1482            let table = Table::new(rows, [Constraint::Length(column_width); 2]);
1483            Widget::render(table, Rect::new(0, 0, width, 2), &mut buf);
1484            assert_eq!(buf, *expected);
1485        }
1486
1487        #[rstest]
1488        #[case(17, 5, vec![
1489                Row::new(vec![
1490                    Cell::new("Cell1").column_span(2),
1491                    Cell::new("Cell2").column_span(1),
1492                ]),
1493                Row::new(vec![
1494                    Cell::new("Cell3").column_span(1),
1495                    Cell::new("Cell4").column_span(1),
1496                    Cell::new("Cell5").column_span(1),
1497                ]),
1498            ], &Buffer::with_lines(["Cell1       Cell2", "Cell3 Cell4 Cell5"]))]
1499        #[case(17, 5, vec![
1500                Row::new(vec![
1501                    Cell::new("Cell1").column_span(1),
1502                    Cell::new("Cell2").column_span(2),
1503                    Cell::new("Cell3").column_span(1),
1504                ]),
1505                Row::new(vec![
1506                    Cell::new("Cell4").column_span(1),
1507                    Cell::new("Cell5").column_span(1),
1508                    Cell::new("Cell6").column_span(1),
1509                ]),
1510            ], &Buffer::with_lines(["Cell1 Cell2      ", "Cell4 Cell5 Cell6"]))]
1511        #[case(15, 5, vec![
1512                Row::new(vec![
1513                    Cell::new("11111111111111111111").column_span(2),
1514                    Cell::new("22222222222222222222").column_span(1),
1515                ]),
1516                Row::new(vec![
1517                    Cell::new("33333333333333333333").column_span(1),
1518                    Cell::new("44444444444444444444").column_span(2),
1519                    Cell::new("55555555555555555555").column_span(1),
1520                ]),
1521            ], &Buffer::with_lines(["1111111111 2222", "3333 4444444444"]))]
1522        fn test_colspans_3_cols<'rows, Rows>(
1523            #[case] width: u16,
1524            #[case] column_width: u16,
1525            #[case] rows: Rows,
1526            #[case] expected: &Buffer,
1527        ) where
1528            Rows: IntoIterator<Item = Row<'rows>>,
1529        {
1530            let mut buf = Buffer::empty(Rect::new(0, 0, width, 2));
1531            let table = Table::new(rows, [Constraint::Length(column_width); 3]);
1532            Widget::render(table, Rect::new(0, 0, width, 2), &mut buf);
1533            assert_eq!(buf, *expected);
1534        }
1535
1536        #[test]
1537        fn render_with_header() {
1538            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1539            let header = Row::new(vec!["Head1", "Head2"]);
1540            let rows = vec![
1541                Row::new(vec!["Cell1", "Cell2"]),
1542                Row::new(vec!["Cell3", "Cell4"]),
1543            ];
1544            let table = Table::new(rows, [Constraint::Length(5); 2]).header(header);
1545            Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
1546            #[rustfmt::skip]
1547            let expected = Buffer::with_lines([
1548                "Head1 Head2    ",
1549                "Cell1 Cell2    ",
1550                "Cell3 Cell4    ",
1551            ]);
1552            assert_eq!(buf, expected);
1553        }
1554
1555        #[test]
1556        fn render_with_footer() {
1557            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1558            let footer = Row::new(vec!["Foot1", "Foot2"]);
1559            let rows = vec![
1560                Row::new(vec!["Cell1", "Cell2"]),
1561                Row::new(vec!["Cell3", "Cell4"]),
1562            ];
1563            let table = Table::new(rows, [Constraint::Length(5); 2]).footer(footer);
1564            Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
1565            #[rustfmt::skip]
1566            let expected = Buffer::with_lines([
1567                "Cell1 Cell2    ",
1568                "Cell3 Cell4    ",
1569                "Foot1 Foot2    ",
1570            ]);
1571            assert_eq!(buf, expected);
1572        }
1573
1574        #[test]
1575        fn render_with_header_and_footer() {
1576            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1577            let header = Row::new(vec!["Head1", "Head2"]);
1578            let footer = Row::new(vec!["Foot1", "Foot2"]);
1579            let rows = vec![Row::new(vec!["Cell1", "Cell2"])];
1580            let table = Table::new(rows, [Constraint::Length(5); 2])
1581                .header(header)
1582                .footer(footer);
1583            Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
1584            #[rustfmt::skip]
1585            let expected = Buffer::with_lines([
1586                "Head1 Head2    ",
1587                "Cell1 Cell2    ",
1588                "Foot1 Foot2    ",
1589            ]);
1590            assert_eq!(buf, expected);
1591        }
1592
1593        #[test]
1594        fn render_with_header_margin() {
1595            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1596            let header = Row::new(vec!["Head1", "Head2"]).bottom_margin(1);
1597            let rows = vec![
1598                Row::new(vec!["Cell1", "Cell2"]),
1599                Row::new(vec!["Cell3", "Cell4"]),
1600            ];
1601            let table = Table::new(rows, [Constraint::Length(5); 2]).header(header);
1602            Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
1603            #[rustfmt::skip]
1604            let expected = Buffer::with_lines([
1605                "Head1 Head2    ",
1606                "               ",
1607                "Cell1 Cell2    ",
1608            ]);
1609            assert_eq!(buf, expected);
1610        }
1611
1612        #[test]
1613        fn render_with_footer_margin() {
1614            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1615            let footer = Row::new(vec!["Foot1", "Foot2"]).top_margin(1);
1616            let rows = vec![Row::new(vec!["Cell1", "Cell2"])];
1617            let table = Table::new(rows, [Constraint::Length(5); 2]).footer(footer);
1618            Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
1619            #[rustfmt::skip]
1620            let expected = Buffer::with_lines([
1621                "Cell1 Cell2    ",
1622                "               ",
1623                "Foot1 Foot2    ",
1624            ]);
1625            assert_eq!(buf, expected);
1626        }
1627
1628        #[test]
1629        fn render_with_row_margin() {
1630            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1631            let rows = vec![
1632                Row::new(vec!["Cell1", "Cell2"]).bottom_margin(1),
1633                Row::new(vec!["Cell3", "Cell4"]),
1634            ];
1635            let table = Table::new(rows, [Constraint::Length(5); 2]);
1636            Widget::render(table, Rect::new(0, 0, 15, 3), &mut buf);
1637            #[rustfmt::skip]
1638            let expected = Buffer::with_lines([
1639                "Cell1 Cell2    ",
1640                "               ",
1641                "Cell3 Cell4    ",
1642            ]);
1643            assert_eq!(buf, expected);
1644        }
1645
1646        #[test]
1647        fn render_with_tall_row() {
1648            let mut buf = Buffer::empty(Rect::new(0, 0, 23, 3));
1649            let rows = vec![
1650                Row::new(vec!["Cell1", "Cell2"]),
1651                Row::new(vec![
1652                    Text::raw("Cell3-Line1\nCell3-Line2\nCell3-Line3"),
1653                    Text::raw("Cell4-Line1\nCell4-Line2\nCell4-Line3"),
1654                ])
1655                .height(3),
1656            ];
1657            let table = Table::new(rows, [Constraint::Length(11); 2]);
1658            Widget::render(table, Rect::new(0, 0, 23, 3), &mut buf);
1659            #[rustfmt::skip]
1660            let expected = Buffer::with_lines([
1661                "Cell1       Cell2      ",
1662                "Cell3-Line1 Cell4-Line1",
1663                "Cell3-Line2 Cell4-Line2",
1664            ]);
1665            assert_eq!(buf, expected);
1666        }
1667
1668        #[test]
1669        fn render_with_alignment() {
1670            let mut buf = Buffer::empty(Rect::new(0, 0, 10, 3));
1671            let rows = vec![
1672                Row::new(vec![Line::from("Left").alignment(Alignment::Left)]),
1673                Row::new(vec![Line::from("Center").alignment(Alignment::Center)]),
1674                Row::new(vec![Line::from("Right").alignment(Alignment::Right)]),
1675            ];
1676            let table = Table::new(rows, [Percentage(100)]);
1677            Widget::render(table, Rect::new(0, 0, 10, 3), &mut buf);
1678            let expected = Buffer::with_lines(["Left      ", "  Center  ", "     Right"]);
1679            assert_eq!(buf, expected);
1680        }
1681
1682        #[test]
1683        fn render_with_overflow_does_not_panic() {
1684            let mut buf = Buffer::empty(Rect::new(0, 0, 20, 3));
1685            let table = Table::new(Vec::<Row>::new(), [Constraint::Min(20); 1])
1686                .header(Row::new([Line::from("").alignment(Alignment::Right)]))
1687                .footer(Row::new([Line::from("").alignment(Alignment::Right)]));
1688            Widget::render(table, Rect::new(0, 0, 20, 3), &mut buf);
1689        }
1690
1691        #[test]
1692        fn render_with_selected_column_and_incorrect_width_count_does_not_panic() {
1693            let mut buf = Buffer::empty(Rect::new(0, 0, 20, 3));
1694            let table = Table::new(
1695                vec![Row::new(vec!["Row1", "Row2", "Row3"])],
1696                [Constraint::Length(10); 1],
1697            );
1698            let mut state = TableState::new().with_selected_column(2);
1699            StatefulWidget::render(table, Rect::new(0, 0, 20, 3), &mut buf, &mut state);
1700        }
1701
1702        #[test]
1703        fn render_with_selected() {
1704            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1705            let rows = vec![
1706                Row::new(vec!["Cell1", "Cell2"]),
1707                Row::new(vec!["Cell3", "Cell4"]),
1708            ];
1709            let table = Table::new(rows, [Constraint::Length(5); 2])
1710                .row_highlight_style(Style::new().red())
1711                .highlight_symbol(">>");
1712            let mut state = TableState::new().with_selected(Some(0));
1713            StatefulWidget::render(table, Rect::new(0, 0, 15, 3), &mut buf, &mut state);
1714            let expected = Buffer::with_lines([
1715                ">>Cell1 Cell2  ".red(),
1716                "  Cell3 Cell4  ".into(),
1717                "               ".into(),
1718            ]);
1719            assert_eq!(buf, expected);
1720        }
1721
1722        #[test]
1723        fn render_with_selected_column() {
1724            let mut buf = Buffer::empty(Rect::new(0, 0, 15, 3));
1725            let rows = vec![
1726                Row::new(vec!["Cell1", "Cell2"]),
1727                Row::new(vec!["Cell3", "Cell4"]),
1728            ];
1729            let table = Table::new(rows, [Constraint::Length(5); 2])
1730                .column_highlight_style(Style::new().blue())
1731                .highlight_symbol(">>");
1732            let mut state = TableState::new().with_selected_column(Some(1));
1733            StatefulWidget::render(table, Rect::new(0, 0, 15, 3), &mut buf, &mut state);
1734            let expected = Buffer::with_lines::<[Line; 3]>([
1735                Line::from(vec![
1736                    "Cell1".into(),
1737                    " ".into(),
1738                    "Cell2".blue(),
1739                    "    ".into(),
1740                ]),
1741                Line::from(vec![
1742                    "Cell3".into(),
1743                    " ".into(),
1744                    "Cell4".blue(),
1745                    "    ".into(),
1746                ]),
1747                Line::from(vec!["      ".into(), "     ".blue(), "    ".into()]),
1748            ]);
1749            assert_eq!(buf, expected);
1750        }
1751
1752        #[test]
1753        fn render_with_selected_cell() {
1754            let mut buf = Buffer::empty(Rect::new(0, 0, 20, 4));
1755            let rows = vec![
1756                Row::new(vec!["Cell1", "Cell2", "Cell3"]),
1757                Row::new(vec!["Cell4", "Cell5", "Cell6"]),
1758                Row::new(vec!["Cell7", "Cell8", "Cell9"]),
1759            ];
1760            let table = Table::new(rows, [Constraint::Length(5); 3])
1761                .highlight_symbol(">>")
1762                .cell_highlight_style(Style::new().green());
1763            let mut state = TableState::new().with_selected_cell((1, 2));
1764            StatefulWidget::render(table, Rect::new(0, 0, 20, 4), &mut buf, &mut state);
1765            let expected = Buffer::with_lines::<[Line; 4]>([
1766                Line::from(vec!["  Cell1 ".into(), "Cell2 ".into(), "Cell3".into()]),
1767                Line::from(vec![">>Cell4 Cell5 ".into(), "Cell6".green(), " ".into()]),
1768                Line::from(vec!["  Cell7 ".into(), "Cell8 ".into(), "Cell9".into()]),
1769                Line::from(vec!["                    ".into()]),
1770            ]);
1771            assert_eq!(buf, expected);
1772        }
1773
1774        #[test]
1775        fn render_with_selected_row_and_column() {
1776            let mut buf = Buffer::empty(Rect::new(0, 0, 20, 4));
1777            let rows = vec![
1778                Row::new(vec!["Cell1", "Cell2", "Cell3"]),
1779                Row::new(vec!["Cell4", "Cell5", "Cell6"]),
1780                Row::new(vec!["Cell7", "Cell8", "Cell9"]),
1781            ];
1782            let table = Table::new(rows, [Constraint::Length(5); 3])
1783                .highlight_symbol(">>")
1784                .row_highlight_style(Style::new().red())
1785                .column_highlight_style(Style::new().blue());
1786            let mut state = TableState::new().with_selected(1).with_selected_column(2);
1787            StatefulWidget::render(table, Rect::new(0, 0, 20, 4), &mut buf, &mut state);
1788            let expected = Buffer::with_lines::<[Line; 4]>([
1789                Line::from(vec!["  Cell1 ".into(), "Cell2 ".into(), "Cell3".blue()]),
1790                Line::from(vec![">>Cell4 Cell5 ".red(), "Cell6".blue(), " ".red()]),
1791                Line::from(vec!["  Cell7 ".into(), "Cell8 ".into(), "Cell9".blue()]),
1792                Line::from(vec!["              ".into(), "     ".blue(), " ".into()]),
1793            ]);
1794            assert_eq!(buf, expected);
1795        }
1796
1797        #[test]
1798        fn render_with_selected_row_and_column_and_cell() {
1799            let mut buf = Buffer::empty(Rect::new(0, 0, 20, 4));
1800            let rows = vec![
1801                Row::new(vec!["Cell1", "Cell2", "Cell3"]),
1802                Row::new(vec!["Cell4", "Cell5", "Cell6"]),
1803                Row::new(vec!["Cell7", "Cell8", "Cell9"]),
1804            ];
1805            let table = Table::new(rows, [Constraint::Length(5); 3])
1806                .highlight_symbol(">>")
1807                .row_highlight_style(Style::new().red())
1808                .column_highlight_style(Style::new().blue())
1809                .cell_highlight_style(Style::new().green());
1810            let mut state = TableState::new().with_selected(1).with_selected_column(2);
1811            StatefulWidget::render(table, Rect::new(0, 0, 20, 4), &mut buf, &mut state);
1812            let expected = Buffer::with_lines::<[Line; 4]>([
1813                Line::from(vec!["  Cell1 ".into(), "Cell2 ".into(), "Cell3".blue()]),
1814                Line::from(vec![">>Cell4 Cell5 ".red(), "Cell6".green(), " ".red()]),
1815                Line::from(vec!["  Cell7 ".into(), "Cell8 ".into(), "Cell9".blue()]),
1816                Line::from(vec!["              ".into(), "     ".blue(), " ".into()]),
1817            ]);
1818            assert_eq!(buf, expected);
1819        }
1820
1821        /// Note that this includes a regression test for a bug where the table would not render the
1822        /// correct rows when there is no selection.
1823        /// <https://github.com/ratatui/ratatui/issues/1179>
1824        #[rstest]
1825        #[case::no_selection(None, 50, ["50", "51", "52", "53", "54"])]
1826        #[case::selection_before_offset(20, 20, ["20", "21", "22", "23", "24"])]
1827        #[case::selection_immediately_before_offset(49, 49, ["49", "50", "51", "52", "53"])]
1828        #[case::selection_at_start_of_offset(50, 50, ["50", "51", "52", "53", "54"])]
1829        #[case::selection_at_end_of_offset(54, 50, ["50", "51", "52", "53", "54"])]
1830        #[case::selection_immediately_after_offset(55, 51, ["51", "52", "53", "54", "55"])]
1831        #[case::selection_after_offset(80, 76, ["76", "77", "78", "79", "80"])]
1832        fn render_with_selection_and_offset<T: Into<Option<usize>>>(
1833            #[case] selected_row: T,
1834            #[case] expected_offset: usize,
1835            #[case] expected_items: [&str; 5],
1836        ) {
1837            // render 100 rows offset at 50, with a selected row
1838            let rows = (0..100).map(|i| Row::new([i.to_string()]));
1839            let table = Table::new(rows, [Constraint::Length(2)]);
1840            let mut buf = Buffer::empty(Rect::new(0, 0, 2, 5));
1841            let mut state = TableState::new()
1842                .with_offset(50)
1843                .with_selected(selected_row.into());
1844
1845            StatefulWidget::render(table.clone(), Rect::new(0, 0, 5, 5), &mut buf, &mut state);
1846
1847            assert_eq!(buf, Buffer::with_lines(expected_items));
1848            assert_eq!(state.offset, expected_offset);
1849        }
1850    }
1851
1852    // test how constraints interact with table column width allocation
1853    mod column_widths {
1854        use super::*;
1855
1856        #[test]
1857        fn length_constraint() {
1858            // without selection, more than needed width
1859            let table = Table::default().widths([Length(4), Length(4)]);
1860            assert_eq!(
1861                table.get_column_widths(20, 0, 0),
1862                [Rect::new(0, 0, 4, 1), Rect::new(5, 0, 4, 1),]
1863            );
1864
1865            // with selection, more than needed width
1866            let table = Table::default().widths([Length(4), Length(4)]);
1867            assert_eq!(
1868                table.get_column_widths(20, 3, 0),
1869                [Rect::new(3, 0, 4, 1), Rect::new(8, 0, 4, 1)]
1870            );
1871
1872            // without selection, less than needed width
1873            let table = Table::default().widths([Length(4), Length(4)]);
1874            assert_eq!(
1875                table.get_column_widths(7, 0, 0),
1876                [Rect::new(0, 0, 3, 1), Rect::new(4, 0, 3, 1)]
1877            );
1878
1879            // with selection, less than needed width
1880            // <--------7px-------->
1881            // ┌────────┐x┌────────┐
1882            // │ (3, 2) │x│ (6, 1) │
1883            // └────────┘x└────────┘
1884            // column spacing (i.e. `x`) is always prioritized
1885            let table = Table::default().widths([Length(4), Length(4)]);
1886            assert_eq!(
1887                table.get_column_widths(7, 3, 0),
1888                [Rect::new(3, 0, 2, 1), Rect::new(6, 0, 1, 1)]
1889            );
1890        }
1891
1892        #[test]
1893        fn max_constraint() {
1894            // without selection, more than needed width
1895            let table = Table::default().widths([Max(4), Max(4)]);
1896            assert_eq!(
1897                table.get_column_widths(20, 0, 0),
1898                [Rect::new(0, 0, 4, 1), Rect::new(5, 0, 4, 1)]
1899            );
1900
1901            // with selection, more than needed width
1902            let table = Table::default().widths([Max(4), Max(4)]);
1903            assert_eq!(
1904                table.get_column_widths(20, 3, 0),
1905                [Rect::new(3, 0, 4, 1), Rect::new(8, 0, 4, 1)]
1906            );
1907
1908            // without selection, less than needed width
1909            let table = Table::default().widths([Max(4), Max(4)]);
1910            assert_eq!(
1911                table.get_column_widths(7, 0, 0),
1912                [Rect::new(0, 0, 3, 1), Rect::new(4, 0, 3, 1)]
1913            );
1914
1915            // with selection, less than needed width
1916            let table = Table::default().widths([Max(4), Max(4)]);
1917            assert_eq!(
1918                table.get_column_widths(7, 3, 0),
1919                [Rect::new(3, 0, 2, 1), Rect::new(6, 0, 1, 1)]
1920            );
1921        }
1922
1923        #[test]
1924        fn min_constraint() {
1925            // in its currently stage, the "Min" constraint does not grow to use the possible
1926            // available length and enabling "expand_to_fill" will just stretch the last
1927            // constraint and not split it with all available constraints
1928
1929            // without selection, more than needed width
1930            let table = Table::default().widths([Min(4), Min(4)]);
1931            assert_eq!(
1932                table.get_column_widths(20, 0, 0),
1933                [Rect::new(0, 0, 10, 1), Rect::new(11, 0, 9, 1)]
1934            );
1935
1936            // with selection, more than needed width
1937            let table = Table::default().widths([Min(4), Min(4)]);
1938            assert_eq!(
1939                table.get_column_widths(20, 3, 0),
1940                [Rect::new(3, 0, 8, 1), Rect::new(12, 0, 8, 1)]
1941            );
1942
1943            // without selection, less than needed width
1944            // allocates spacer
1945            let table = Table::default().widths([Min(4), Min(4)]);
1946            assert_eq!(
1947                table.get_column_widths(7, 0, 0),
1948                [Rect::new(0, 0, 3, 1), Rect::new(4, 0, 3, 1)]
1949            );
1950
1951            // with selection, less than needed width
1952            // always allocates selection and spacer
1953            let table = Table::default().widths([Min(4), Min(4)]);
1954            assert_eq!(
1955                table.get_column_widths(7, 3, 0),
1956                [Rect::new(3, 0, 2, 1), Rect::new(6, 0, 1, 1)]
1957            );
1958        }
1959
1960        #[test]
1961        fn percentage_constraint() {
1962            // without selection, more than needed width
1963            let table = Table::default().widths([Percentage(30), Percentage(30)]);
1964            assert_eq!(
1965                table.get_column_widths(20, 0, 0),
1966                [Rect::new(0, 0, 6, 1), Rect::new(7, 0, 6, 1)]
1967            );
1968
1969            // with selection, more than needed width
1970            let table = Table::default().widths([Percentage(30), Percentage(30)]);
1971            assert_eq!(
1972                table.get_column_widths(20, 3, 0),
1973                [Rect::new(3, 0, 5, 1), Rect::new(9, 0, 5, 1)]
1974            );
1975
1976            // without selection, less than needed width
1977            // rounds from positions: [0.0, 0.0, 2.1, 3.1, 5.2, 7.0]
1978            let table = Table::default().widths([Percentage(30), Percentage(30)]);
1979            assert_eq!(
1980                table.get_column_widths(7, 0, 0),
1981                [Rect::new(0, 0, 2, 1), Rect::new(3, 0, 2, 1)]
1982            );
1983
1984            // with selection, less than needed width
1985            // rounds from positions: [0.0, 3.0, 5.1, 6.1, 7.0, 7.0]
1986            let table = Table::default().widths([Percentage(30), Percentage(30)]);
1987            assert_eq!(
1988                table.get_column_widths(7, 3, 0),
1989                [Rect::new(3, 0, 1, 1), Rect::new(5, 0, 1, 1)]
1990            );
1991        }
1992
1993        #[test]
1994        fn ratio_constraint() {
1995            // without selection, more than needed width
1996            // rounds from positions: [0.00, 0.00, 6.67, 7.67, 14.33]
1997            let table = Table::default().widths([Ratio(1, 3), Ratio(1, 3)]);
1998            assert_eq!(
1999                table.get_column_widths(20, 0, 0),
2000                [Rect::new(0, 0, 7, 1), Rect::new(8, 0, 6, 1)]
2001            );
2002
2003            // with selection, more than needed width
2004            // rounds from positions: [0.00, 3.00, 10.67, 17.33, 20.00]
2005            let table = Table::default().widths([Ratio(1, 3), Ratio(1, 3)]);
2006            assert_eq!(
2007                table.get_column_widths(20, 3, 0),
2008                [Rect::new(3, 0, 6, 1), Rect::new(10, 0, 5, 1)]
2009            );
2010
2011            // without selection, less than needed width
2012            // rounds from positions: [0.00, 2.33, 3.33, 5.66, 7.00]
2013            let table = Table::default().widths([Ratio(1, 3), Ratio(1, 3)]);
2014            assert_eq!(
2015                table.get_column_widths(7, 0, 0),
2016                [Rect::new(0, 0, 2, 1), Rect::new(3, 0, 3, 1)]
2017            );
2018
2019            // with selection, less than needed width
2020            // rounds from positions: [0.00, 3.00, 5.33, 6.33, 7.00, 7.00]
2021            let table = Table::default().widths([Ratio(1, 3), Ratio(1, 3)]);
2022            assert_eq!(
2023                table.get_column_widths(7, 3, 0),
2024                [Rect::new(3, 0, 1, 1), Rect::new(5, 0, 2, 1)]
2025            );
2026        }
2027
2028        /// When more width is available than requested, the behavior is controlled by flex
2029        #[test]
2030        fn underconstrained_flex() {
2031            let table = Table::default().widths([Min(10), Min(10), Min(1)]);
2032            assert_eq!(
2033                table.get_column_widths(62, 0, 0),
2034                &[
2035                    Rect::new(0, 0, 20, 1),
2036                    Rect::new(21, 0, 20, 1),
2037                    Rect::new(42, 0, 20, 1)
2038                ]
2039            );
2040
2041            let table = Table::default()
2042                .widths([Min(10), Min(10), Min(1)])
2043                .flex(Flex::Legacy);
2044            assert_eq!(
2045                table.get_column_widths(62, 0, 0),
2046                &[
2047                    Rect::new(0, 0, 10, 1),
2048                    Rect::new(11, 0, 10, 1),
2049                    Rect::new(22, 0, 40, 1)
2050                ]
2051            );
2052
2053            let table = Table::default()
2054                .widths([Min(10), Min(10), Min(1)])
2055                .flex(Flex::SpaceBetween);
2056            assert_eq!(
2057                table.get_column_widths(62, 0, 0),
2058                &[
2059                    Rect::new(0, 0, 20, 1),
2060                    Rect::new(21, 0, 20, 1),
2061                    Rect::new(42, 0, 20, 1)
2062                ]
2063            );
2064        }
2065
2066        #[test]
2067        fn underconstrained_segment_size() {
2068            let table = Table::default().widths([Min(10), Min(10), Min(1)]);
2069            assert_eq!(
2070                table.get_column_widths(62, 0, 0),
2071                &[
2072                    Rect::new(0, 0, 20, 1),
2073                    Rect::new(21, 0, 20, 1),
2074                    Rect::new(42, 0, 20, 1)
2075                ]
2076            );
2077
2078            let table = Table::default()
2079                .widths([Min(10), Min(10), Min(1)])
2080                .flex(Flex::Legacy);
2081            assert_eq!(
2082                table.get_column_widths(62, 0, 0),
2083                &[
2084                    Rect::new(0, 0, 10, 1),
2085                    Rect::new(11, 0, 10, 1),
2086                    Rect::new(22, 0, 40, 1)
2087                ]
2088            );
2089        }
2090
2091        #[test]
2092        fn no_constraint_with_rows() {
2093            let table = Table::default()
2094                .rows(vec![
2095                    Row::new(vec!["a", "b"]),
2096                    Row::new(vec!["c", "d", "e"]),
2097                ])
2098                // rows should get precedence over header
2099                .header(Row::new(vec!["f", "g"]))
2100                .footer(Row::new(vec!["h", "i"]))
2101                .column_spacing(0);
2102            assert_eq!(
2103                table.get_column_widths(30, 0, 3),
2104                &[
2105                    Rect::new(0, 0, 10, 1),
2106                    Rect::new(10, 0, 10, 1),
2107                    Rect::new(20, 0, 10, 1)
2108                ]
2109            );
2110        }
2111
2112        #[test]
2113        fn no_constraint_with_header() {
2114            let table = Table::default()
2115                .rows(vec![])
2116                .header(Row::new(vec!["f", "g"]))
2117                .column_spacing(0);
2118            assert_eq!(
2119                table.get_column_widths(10, 0, 2),
2120                [Rect::new(0, 0, 5, 1), Rect::new(5, 0, 5, 1)]
2121            );
2122        }
2123
2124        #[test]
2125        fn no_constraint_with_footer() {
2126            let table = Table::default()
2127                .rows(vec![])
2128                .footer(Row::new(vec!["h", "i"]))
2129                .column_spacing(0);
2130            assert_eq!(
2131                table.get_column_widths(10, 0, 2),
2132                [Rect::new(0, 0, 5, 1), Rect::new(5, 0, 5, 1)]
2133            );
2134        }
2135
2136        #[track_caller]
2137        fn test_table_with_selection<'line, Lines>(
2138            highlight_spacing: HighlightSpacing,
2139            columns: u16,
2140            spacing: u16,
2141            selection: Option<usize>,
2142            expected: Lines,
2143        ) where
2144            Lines: IntoIterator,
2145            Lines::Item: Into<Line<'line>>,
2146        {
2147            let table = Table::default()
2148                .rows(vec![Row::new(vec!["ABCDE", "12345"])])
2149                .highlight_spacing(highlight_spacing)
2150                .highlight_symbol(">>>")
2151                .column_spacing(spacing);
2152            let area = Rect::new(0, 0, columns, 3);
2153            let mut buf = Buffer::empty(area);
2154            let mut state = TableState::default().with_selected(selection);
2155            StatefulWidget::render(table, area, &mut buf, &mut state);
2156            assert_eq!(buf, Buffer::with_lines(expected));
2157        }
2158
2159        #[test]
2160        fn excess_area_highlight_symbol_and_column_spacing_allocation() {
2161            // no highlight_symbol rendered ever
2162            test_table_with_selection(
2163                HighlightSpacing::Never,
2164                15,   // width
2165                0,    // spacing
2166                None, // selection
2167                [
2168                    "ABCDE  12345   ", /* default layout is Flex::Start but columns length
2169                                        * constraints are calculated as `max_area / n_columns`,
2170                                        * i.e. they are distributed amongst available space */
2171                    "               ", // row 2
2172                    "               ", // row 3
2173                ],
2174            );
2175
2176            let table = Table::default()
2177                .rows(vec![Row::new(vec!["ABCDE", "12345"])])
2178                .widths([5, 5])
2179                .column_spacing(0);
2180            let area = Rect::new(0, 0, 15, 3);
2181            let mut buf = Buffer::empty(area);
2182            Widget::render(table, area, &mut buf);
2183            let expected = Buffer::with_lines([
2184                "ABCDE12345     ", /* As reference, this is what happens when you manually
2185                                    * specify widths */
2186                "               ", // row 2
2187                "               ", // row 3
2188            ]);
2189            assert_eq!(buf, expected);
2190
2191            // no highlight_symbol rendered ever
2192            test_table_with_selection(
2193                HighlightSpacing::Never,
2194                15,      // width
2195                0,       // spacing
2196                Some(0), // selection
2197                [
2198                    "ABCDE  12345   ", // row 1
2199                    "               ", // row 2
2200                    "               ", // row 3
2201                ],
2202            );
2203
2204            // no highlight_symbol rendered because no selection is made
2205            test_table_with_selection(
2206                HighlightSpacing::WhenSelected,
2207                15,   // width
2208                0,    // spacing
2209                None, // selection
2210                [
2211                    "ABCDE  12345   ", // row 1
2212                    "               ", // row 2
2213                    "               ", // row 3
2214                ],
2215            );
2216            // highlight_symbol rendered because selection is made
2217            test_table_with_selection(
2218                HighlightSpacing::WhenSelected,
2219                15,      // width
2220                0,       // spacing
2221                Some(0), // selection
2222                [
2223                    ">>>ABCDE 12345 ", // row 1
2224                    "               ", // row 2
2225                    "               ", // row 3
2226                ],
2227            );
2228
2229            // highlight_symbol always rendered even no selection is made
2230            test_table_with_selection(
2231                HighlightSpacing::Always,
2232                15,   // width
2233                0,    // spacing
2234                None, // selection
2235                [
2236                    "   ABCDE 12345 ", // row 1
2237                    "               ", // row 2
2238                    "               ", // row 3
2239                ],
2240            );
2241
2242            // no highlight_symbol rendered because no selection is made
2243            test_table_with_selection(
2244                HighlightSpacing::Always,
2245                15,      // width
2246                0,       // spacing
2247                Some(0), // selection
2248                [
2249                    ">>>ABCDE 12345 ", // row 1
2250                    "               ", // row 2
2251                    "               ", // row 3
2252                ],
2253            );
2254        }
2255
2256        #[expect(clippy::too_many_lines)]
2257        #[test]
2258        fn insufficient_area_highlight_symbol_and_column_spacing_allocation() {
2259            // column spacing is prioritized over every other constraint
2260            test_table_with_selection(
2261                HighlightSpacing::Never,
2262                10,   // width
2263                1,    // spacing
2264                None, // selection
2265                [
2266                    "ABCDE 1234", // spacing is prioritized and column is cut
2267                    "          ", // row 2
2268                    "          ", // row 3
2269                ],
2270            );
2271            test_table_with_selection(
2272                HighlightSpacing::WhenSelected,
2273                10,   // width
2274                1,    // spacing
2275                None, // selection
2276                [
2277                    "ABCDE 1234", // spacing is prioritized and column is cut
2278                    "          ", // row 2
2279                    "          ", // row 3
2280                ],
2281            );
2282
2283            // this test checks that space for highlight_symbol space is always allocated.
2284            // this test also checks that space for column is allocated.
2285            //
2286            // Space for highlight_symbol is allocated first by splitting horizontal space
2287            // into highlight_symbol area and column area.
2288            // Then in a separate step, column widths are calculated.
2289            // column spacing is prioritized when column widths are calculated and last column here
2290            // ends up with just 1 wide
2291            test_table_with_selection(
2292                HighlightSpacing::Always,
2293                10,   // width
2294                1,    // spacing
2295                None, // selection
2296                [
2297                    "   ABC 123", // highlight_symbol and spacing are prioritized
2298                    "          ", // row 2
2299                    "          ", // row 3
2300                ],
2301            );
2302
2303            // the following are specification tests
2304            test_table_with_selection(
2305                HighlightSpacing::Always,
2306                9,    // width
2307                1,    // spacing
2308                None, // selection
2309                [
2310                    "   ABC 12", // highlight_symbol and spacing are prioritized
2311                    "         ", // row 2
2312                    "         ", // row 3
2313                ],
2314            );
2315            test_table_with_selection(
2316                HighlightSpacing::Always,
2317                8,    // width
2318                1,    // spacing
2319                None, // selection
2320                [
2321                    "   AB 12", // highlight_symbol and spacing are prioritized
2322                    "        ", // row 2
2323                    "        ", // row 3
2324                ],
2325            );
2326            test_table_with_selection(
2327                HighlightSpacing::Always,
2328                7,    // width
2329                1,    // spacing
2330                None, // selection
2331                [
2332                    "   AB 1", // highlight_symbol and spacing are prioritized
2333                    "       ", // row 2
2334                    "       ", // row 3
2335                ],
2336            );
2337
2338            let table = Table::default()
2339                .rows(vec![Row::new(vec!["ABCDE", "12345"])])
2340                .highlight_spacing(HighlightSpacing::Always)
2341                .flex(Flex::Legacy)
2342                .highlight_symbol(">>>")
2343                .column_spacing(1);
2344            let area = Rect::new(0, 0, 10, 3);
2345            let mut buf = Buffer::empty(area);
2346            Widget::render(table, area, &mut buf);
2347            // highlight_symbol and spacing are prioritized but columns are evenly distributed
2348            #[rustfmt::skip]
2349            let expected = Buffer::with_lines([
2350                "   ABCDE 1",
2351                "          ",
2352                "          ",
2353            ]);
2354            assert_eq!(buf, expected);
2355
2356            let table = Table::default()
2357                .rows(vec![Row::new(vec!["ABCDE", "12345"])])
2358                .highlight_spacing(HighlightSpacing::Always)
2359                .flex(Flex::Start)
2360                .highlight_symbol(">>>")
2361                .column_spacing(1);
2362            let area = Rect::new(0, 0, 10, 3);
2363            let mut buf = Buffer::empty(area);
2364            Widget::render(table, area, &mut buf);
2365            // highlight_symbol and spacing are prioritized but columns are evenly distributed
2366            #[rustfmt::skip]
2367            let expected = Buffer::with_lines([
2368                "   ABC 123",
2369                "          ",
2370                "          ",
2371            ]);
2372            assert_eq!(buf, expected);
2373
2374            test_table_with_selection(
2375                HighlightSpacing::Never,
2376                10,      // width
2377                1,       // spacing
2378                Some(0), // selection
2379                [
2380                    "ABCDE 1234", // spacing is prioritized
2381                    "          ",
2382                    "          ",
2383                ],
2384            );
2385
2386            test_table_with_selection(
2387                HighlightSpacing::WhenSelected,
2388                10,      // width
2389                1,       // spacing
2390                Some(0), // selection
2391                [
2392                    ">>>ABC 123", // row 1
2393                    "          ", // row 2
2394                    "          ", // row 3
2395                ],
2396            );
2397
2398            test_table_with_selection(
2399                HighlightSpacing::Always,
2400                10,      // width
2401                1,       // spacing
2402                Some(0), // selection
2403                [
2404                    ">>>ABC 123", // highlight column and spacing are prioritized
2405                    "          ", // row 2
2406                    "          ", // row 3
2407                ],
2408            );
2409        }
2410
2411        #[test]
2412        fn insufficient_area_highlight_symbol_allocation_with_no_column_spacing() {
2413            test_table_with_selection(
2414                HighlightSpacing::Never,
2415                10,   // width
2416                0,    // spacing
2417                None, // selection
2418                [
2419                    "ABCDE12345", // row 1
2420                    "          ", // row 2
2421                    "          ", // row 3
2422                ],
2423            );
2424            test_table_with_selection(
2425                HighlightSpacing::WhenSelected,
2426                10,   // width
2427                0,    // spacing
2428                None, // selection
2429                [
2430                    "ABCDE12345", // row 1
2431                    "          ", // row 2
2432                    "          ", // row 3
2433                ],
2434            );
2435            // highlight symbol spacing is prioritized over all constraints
2436            // even if the constraints are fixed length
2437            // this is because highlight_symbol column is separated _before_ any of the constraint
2438            // widths are calculated
2439            test_table_with_selection(
2440                HighlightSpacing::Always,
2441                10,   // width
2442                0,    // spacing
2443                None, // selection
2444                [
2445                    "   ABCD123", // highlight column and spacing are prioritized
2446                    "          ", // row 2
2447                    "          ", // row 3
2448                ],
2449            );
2450            test_table_with_selection(
2451                HighlightSpacing::Never,
2452                10,      // width
2453                0,       // spacing
2454                Some(0), // selection
2455                [
2456                    "ABCDE12345", // row 1
2457                    "          ", // row 2
2458                    "          ", // row 3
2459                ],
2460            );
2461            test_table_with_selection(
2462                HighlightSpacing::WhenSelected,
2463                10,      // width
2464                0,       // spacing
2465                Some(0), // selection
2466                [
2467                    ">>>ABCD123", // highlight column and spacing are prioritized
2468                    "          ", // row 2
2469                    "          ", // row 3
2470                ],
2471            );
2472            test_table_with_selection(
2473                HighlightSpacing::Always,
2474                10,      // width
2475                0,       // spacing
2476                Some(0), // selection
2477                [
2478                    ">>>ABCD123", // highlight column and spacing are prioritized
2479                    "          ", // row 2
2480                    "          ", // row 3
2481                ],
2482            );
2483        }
2484    }
2485
2486    #[test]
2487    fn stylize() {
2488        assert_eq!(
2489            Table::new(vec![Row::new(vec![Cell::from("")])], [Percentage(100)])
2490                .black()
2491                .on_white()
2492                .bold()
2493                .not_crossed_out()
2494                .style,
2495            Style::default()
2496                .fg(Color::Black)
2497                .bg(Color::White)
2498                .add_modifier(Modifier::BOLD)
2499                .remove_modifier(Modifier::CROSSED_OUT)
2500        );
2501    }
2502
2503    #[rstest]
2504    #[case::no_columns(vec![], vec![], vec![], 0)]
2505    #[case::only_header(vec!["H1", "H2"], vec![], vec![], 2)]
2506    #[case::only_rows(
2507        vec![],
2508        vec![vec!["C1", "C2"], vec!["C1", "C2", "C3"]],
2509        vec![],
2510        3
2511    )]
2512    #[case::only_footer(vec![], vec![], vec!["F1", "F2", "F3", "F4"], 4)]
2513    #[case::rows_longer(
2514        vec!["H1", "H2", "H3", "H4"],
2515        vec![vec!["C1", "C2"],vec!["C1", "C2", "C3"]],
2516        vec!["F1", "F2"],
2517        4
2518    )]
2519    #[case::rows_longer(
2520        vec!["H1", "H2"],
2521        vec![vec!["C1", "C2"], vec!["C1", "C2", "C3", "C4"]],
2522        vec!["F1", "F2"],
2523        4
2524    )]
2525    #[case::footer_longer(
2526        vec!["H1", "H2"],
2527        vec![vec!["C1", "C2"], vec!["C1", "C2", "C3"]],
2528        vec!["F1", "F2", "F3", "F4"],
2529        4
2530    )]
2531
2532    fn column_count(
2533        #[case] header: Vec<&str>,
2534        #[case] rows: Vec<Vec<&str>>,
2535        #[case] footer: Vec<&str>,
2536        #[case] expected: usize,
2537    ) {
2538        let header = Row::new(header);
2539        let footer = Row::new(footer);
2540        let rows: Vec<Row> = rows.into_iter().map(Row::new).collect();
2541        let table = Table::new(rows, Vec::<Constraint>::new())
2542            .header(header)
2543            .footer(footer);
2544        let column_count = table.column_count();
2545        assert_eq!(column_count, expected);
2546    }
2547
2548    #[test]
2549    fn render_in_minimal_buffer() {
2550        let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
2551        let rows = vec![
2552            Row::new(vec!["Cell1", "Cell2", "Cell3"]),
2553            Row::new(vec!["Cell4", "Cell5", "Cell6"]),
2554        ];
2555        let table = Table::new(rows, [Constraint::Length(10); 3])
2556            .header(Row::new(vec!["Header1", "Header2", "Header3"]))
2557            .footer(Row::new(vec!["Footer1", "Footer2", "Footer3"]));
2558        // This should not panic, even if the buffer is too small to render the table.
2559        Widget::render(table, buffer.area, &mut buffer);
2560        assert_eq!(buffer, Buffer::with_lines([" "]));
2561    }
2562
2563    #[test]
2564    fn render_in_zero_size_buffer() {
2565        let mut buffer = Buffer::empty(Rect::ZERO);
2566        let rows = vec![
2567            Row::new(vec!["Cell1", "Cell2", "Cell3"]),
2568            Row::new(vec!["Cell4", "Cell5", "Cell6"]),
2569        ];
2570        let table = Table::new(rows, [Constraint::Length(10); 3])
2571            .header(Row::new(vec!["Header1", "Header2", "Header3"]))
2572            .footer(Row::new(vec!["Footer1", "Footer2", "Footer3"]));
2573        // This should not panic, even if the buffer has zero size.
2574        Widget::render(table, buffer.area, &mut buffer);
2575    }
2576
2577    #[test]
2578    fn get_area_for_column_span_one_no_more_columns() {
2579        let columns = [];
2580        let column_span = Table::get_cell_area(&mut columns.iter(), 1, 1);
2581        assert!(column_span.is_none());
2582    }
2583
2584    #[test]
2585    fn get_area_for_column_span_two_no_more_columns() {
2586        let columns = [];
2587        let column_span = Table::get_cell_area(&mut columns.iter(), 2, 1);
2588        assert!(column_span.is_none());
2589    }
2590
2591    #[rstest]
2592    #[case(&[Rect{x: 3, width: 2, y: 0, height: 1}, Rect{x: 3, width: 2, y: 0, height: 1}, Rect{x: 3, width: 2, y: 0, height: 1}], 2, 5)]
2593    #[case(&[Rect{x: 3, width: 2, y: 0, height: 1}, Rect{x: 3, width: 2, y: 0, height: 1}], 2, 5,)]
2594    #[case(&[Rect{x: 3, width: 2, y: 0, height: 1}, Rect{x: 3, width: 2, y: 0, height: 1}], 1, 2)]
2595    #[case(&[Rect{x: 3, width: 2, y: 0, height: 1}, Rect{x: 3, width: 2, y: 0, height: 1}], 3, 5)]
2596    #[case(&[Rect{x: 3, width: 2, y: 0, height: 1}], 1, 2)]
2597    #[case(&[Rect{x: 3, width: 2, y: 0, height: 1}], 2, 2)]
2598    #[case(&[
2599        Rect{x: 3, width: 2, y: 0, height: 1},
2600        Rect{x: 3, width: 2, y: 0, height: 1},
2601        Rect{x: 3, width: 2, y: 0, height: 1},
2602        Rect{x: 3, width: 2, y: 0, height: 1},
2603            ], 3, 8)]
2604    #[case(&[
2605        Rect{x: 3, width: 2, y: 0, height: 1},
2606        Rect{x: 3, width: 2, y: 0, height: 1},
2607        Rect{x: 3, width: 2, y: 0, height: 1},
2608            ], 3, 8)]
2609    fn test_colspan_width_single_column_spacing(
2610        #[case] columns: &[Rect],
2611        #[case] column_span: u16,
2612        #[case] expected_column_width: u16,
2613    ) {
2614        let column_span = Table::get_cell_area(&mut columns.iter(), column_span, 1);
2615        assert!(column_span.is_some());
2616        assert_eq!(column_span.unwrap().width, expected_column_width);
2617    }
2618
2619    #[rstest]
2620    #[case(&[Rect{x: 3, width: 2, y: 0, height: 1}, Rect{x: 3, width: 2, y: 0, height: 1}, Rect{x: 3, width: 2, y: 0, height: 1}], 3, 10)]
2621    #[case(&[Rect{x: 3, width: 2, y: 0, height: 1}], 3, 2)]
2622    fn test_colspan_width_two_column_spacing(
2623        #[case] columns: &[Rect],
2624        #[case] column_span: u16,
2625        #[case] expected_column_width: u16,
2626    ) {
2627        let column_span = Table::get_cell_area(&mut columns.iter(), column_span, 2);
2628        assert!(column_span.is_some());
2629        assert_eq!(column_span.unwrap().width, expected_column_width);
2630    }
2631
2632    #[rstest]
2633    #[case(
2634        HighlightSpacing::Always,
2635        15,   // width
2636        1,    // spacing
2637        None, // selection
2638        [
2639            Cell::new("ABCDEFGHIJK").column_span(2),
2640            Cell::new("12345678901"),
2641            Cell::new("XYZXYZXYZXY"),
2642        ],
2643        [
2644            "   ABCDEFGH 123",
2645            "               ", // row 2
2646            "               ", // row 3
2647        ])]
2648    #[case(
2649        HighlightSpacing::Always,
2650        15,      // width
2651        1,       // spacing
2652        Some(0), // selection
2653        [
2654            Cell::new("ABCDEFGHIJK").column_span(2),
2655            Cell::new("12345678901"),
2656            Cell::new("XYZXYZXYZXY"),
2657        ],
2658        [
2659            ">>>ABCDEFGH 123",
2660            "               ", // row 2
2661            "               ", // row 3
2662        ])]
2663    #[case(
2664        HighlightSpacing::WhenSelected,
2665        15,   // width
2666        1,    // spacing
2667        None, // selection
2668        [
2669            Cell::new("ABCDEFGHIJK").column_span(2),
2670            Cell::new("12345678901"),
2671            Cell::new("XYZXYZXYZXY"),
2672        ],
2673        [
2674            "ABCDEFGHIJ 1234",
2675            "               ", // row 2
2676            "               ", // row 3
2677        ])]
2678    #[case(
2679        HighlightSpacing::WhenSelected,
2680        15,      // width
2681        1,       // spacing
2682        Some(0), // selection
2683        [
2684            Cell::new("ABCDEFGHIJK").column_span(2),
2685            Cell::new("12345678901"),
2686            Cell::new("XYZXYZXYZXY"),
2687        ],
2688        [
2689            ">>>ABCDEFGH 123",
2690            "               ", // row 2
2691            "               ", // row 3
2692        ])]
2693    fn test_table_with_selection_and_column_spans<'line, 'cell, Lines, Cells>(
2694        #[case] highlight_spacing: HighlightSpacing,
2695        #[case] columns: u16,
2696        #[case] spacing: u16,
2697        #[case] selection: Option<usize>,
2698        #[case] cells: Cells,
2699        #[case] expected: Lines,
2700    ) where
2701        Cells: IntoIterator,
2702        Cells::Item: Into<Cell<'cell>>,
2703        Lines: IntoIterator,
2704        Lines::Item: Into<Line<'line>>,
2705    {
2706        let table = Table::default()
2707            .rows(vec![Row::new(cells)])
2708            .highlight_spacing(highlight_spacing)
2709            .highlight_symbol(">>>")
2710            .column_spacing(spacing);
2711        let area = Rect::new(0, 0, columns, 3);
2712        let mut buf = Buffer::empty(area);
2713        let mut state = TableState::default().with_selected(selection);
2714        StatefulWidget::render(table, area, &mut buf, &mut state);
2715        assert_eq!(buf, Buffer::with_lines(expected));
2716    }
2717}