ratatui/widgets/table/
table.rs

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