Skip to main content

lipgloss_table/
lib.rs

1//! # lipgloss-table
2//!
3//! A flexible and powerful table rendering library for terminal applications.
4//!
5//! This crate provides a comprehensive table rendering system with advanced styling,
6//! layout options, and terminal-aware text handling. It's designed to work seamlessly
7//! with the `lipgloss` styling library to create beautiful terminal user interfaces.
8//!
9//! ## Features
10//!
11//! - **Flexible Table Construction**: Build tables using a fluent builder pattern
12//! - **Advanced Styling**: Apply different styles to headers, rows, and individual cells
13//! - **Border Customization**: Control all aspects of table borders and separators
14//! - **Responsive Layout**: Automatic width detection and content wrapping/truncation
15//! - **Height Constraints**: Set maximum heights with automatic scrolling and overflow indicators
16//! - **ANSI-Aware**: Proper handling of ANSI escape sequences in content
17//! - **Memory Safe**: Built-in protections against memory exhaustion from malicious input
18//!
19//! ## Quick Start
20//!
21//! ```rust
22//! use lipgloss_table::{Table, HEADER_ROW, header_row_style};
23//! use lipgloss::{Style, Color};
24//!
25//! // Create a simple table
26//! let mut table = Table::new()
27//!     .headers(vec!["Name", "Age", "City"])
28//!     .row(vec!["Alice", "30", "New York"])
29//!     .row(vec!["Bob", "25", "London"])
30//!     .style_func(header_row_style);
31//!
32//! println!("{}", table.render());
33//! ```
34//!
35//! ## Advanced Usage
36//!
37//! ### Custom Styling
38//!
39//! ```rust
40//! use lipgloss_table::{Table, HEADER_ROW};
41//! use lipgloss::{Style, Color};
42//!
43//! let style_func = |row: i32, col: usize| {
44//!     match row {
45//!         HEADER_ROW => Style::new().bold(true).foreground(Color::from("#FFFFFF")),
46//!         _ if row % 2 == 0 => Style::new().background(Color::from("#F0F0F0")),
47//!         _ => Style::new(),
48//!     }
49//! };
50//!
51//! let mut table = Table::new()
52//!     .headers(vec!["Product", "Price", "Stock"])
53//!     .rows(vec![
54//!         vec!["Widget A", "$10.99", "50"],
55//!         vec!["Widget B", "$15.99", "25"],
56//!         vec!["Widget C", "$8.99", "100"],
57//!     ])
58//!     .style_func(style_func)
59//!     .width(40);
60//!
61//! println!("{}", table.render());
62//! ```
63//!
64//! ### Height-Constrained Tables with Scrolling
65//!
66//! ```rust
67//! use lipgloss_table::Table;
68//!
69//! let mut table = Table::new()
70//!     .headers(vec!["Item", "Description"])
71//!     .height(10)  // Limit table to 10 lines
72//!     .offset(5);  // Skip first 5 rows (scrolling)
73//!
74//! // Add many rows...
75//! for i in 1..=100 {
76//!     table = table.row(vec![format!("Item {}", i), "Description".to_string()]);
77//! }
78//!
79//! println!("{}", table.render());
80//! println!("Table height: {}", table.compute_height());
81//! ```
82//!
83//! ## Predefined Style Functions
84//!
85//! The crate includes several predefined styling functions:
86//!
87//! - [`default_styles`]: Basic styling with no attributes
88//! - [`header_row_style`]: Bold headers with default data rows
89//! - [`zebra_style`]: Alternating row backgrounds for better readability
90//! - [`minimal_style`]: Subtle styling with muted colors
91//! - [`column_style_func`]: Factory for creating column-specific styles
92//!
93//! ## Integration with lipgloss
94//!
95//! This crate is designed to work seamlessly with the `lipgloss` styling library.
96//! All styling functions receive `lipgloss::Style` objects and can use the full
97//! range of lipgloss features including colors, borders, padding, and alignment.
98
99#![warn(missing_docs)]
100
101/// Internal module for table resizing logic and column width calculations.
102pub mod resizing;
103
104/// Internal module for data handling and row management.
105pub mod rows;
106
107/// Internal utility functions for table operations.
108pub mod util;
109
110use lipgloss::security::{safe_repeat, safe_str_repeat};
111use lipgloss::{Border, Style};
112use std::fmt;
113
114// Re-export the main types and functions
115pub use resizing::{Resizer, ResizerColumn};
116pub use rows::{data_to_matrix, Data, Filter, StringData};
117
118/// HeaderRow denotes the header's row index used when rendering headers.
119/// Use this value when looking to customize header styles in StyleFunc.
120pub const HEADER_ROW: i32 = -1;
121
122/// StyleFunc is the style function that determines the style of a Cell.
123///
124/// It takes the row and column of the cell as an input and determines the
125/// lipgloss Style to use for that cell position.
126///
127/// Example:
128///
129/// ```rust
130/// use lipgloss::{Style, Color};
131/// use lipgloss_table::{Table, HEADER_ROW};
132///
133/// let style_func = |row: i32, col: usize| {
134///     match row {
135///         HEADER_ROW => Style::new().bold(true),
136///         _ if row % 2 == 0 => Style::new().foreground(Color::from("#888888")),
137///         _ => Style::new(),
138///     }
139/// };
140/// ```
141pub type StyleFunc = fn(row: i32, col: usize) -> Style;
142
143/// A basic style function that applies no formatting to any cells.
144///
145/// This function serves as the default styling approach, returning a plain
146/// `Style` with no attributes for all table cells. It's useful as a starting
147/// point or when you want completely unstyled table content.
148///
149/// # Arguments
150///
151/// * `_row` - The row index (unused, but required by the `StyleFunc` signature)
152/// * `_col` - The column index (unused, but required by the `StyleFunc` signature)
153///
154/// # Returns
155///
156/// A new `Style` instance with no formatting applied.
157///
158/// # Examples
159///
160/// ```rust
161/// use lipgloss_table::{Table, default_styles};
162///
163/// let mut table = Table::new()
164///     .headers(vec!["Name", "Age"])
165///     .row(vec!["Alice", "30"])
166///     .style_func(default_styles);
167///
168/// println!("{}", table.render());
169/// ```
170pub fn default_styles(_row: i32, _col: usize) -> Style {
171    Style::new()
172}
173
174/// A style function that makes header rows bold while leaving data rows unstyled.
175///
176/// This function provides a simple but effective styling approach by applying
177/// bold formatting to header rows (identified by `HEADER_ROW`) while leaving
178/// all data rows with default styling. This creates a clear visual distinction
179/// between headers and content.
180///
181/// # Arguments
182///
183/// * `row` - The row index to style (headers use `HEADER_ROW` constant)
184/// * `_col` - The column index (unused, but required by the `StyleFunc` signature)
185///
186/// # Returns
187///
188/// * Bold `Style` for header rows (`HEADER_ROW`)
189/// * Default `Style` for all data rows
190///
191/// # Examples
192///
193/// ```rust
194/// use lipgloss_table::{Table, header_row_style};
195///
196/// let mut table = Table::new()
197///     .headers(vec!["Product", "Price", "Stock"])
198///     .row(vec!["Widget A", "$10.99", "50"])
199///     .row(vec!["Widget B", "$15.99", "25"])
200///     .style_func(header_row_style);
201///
202/// println!("{}", table.render());
203/// ```
204pub fn header_row_style(row: i32, _col: usize) -> Style {
205    match row {
206        HEADER_ROW => Style::new().bold(true),
207        _ => Style::new(),
208    }
209}
210
211/// A style function that creates alternating row backgrounds (zebra striping) for improved readability.
212///
213/// This function applies a "zebra stripe" pattern to table rows, alternating between
214/// a default background and a subtle background color for even-numbered rows. The header
215/// row receives bold styling. The background colors are adaptive, changing based on
216/// whether the terminal has a light or dark theme.
217///
218/// # Row Pattern
219///
220/// * Header row: Bold text
221/// * Even data rows (0, 2, 4...): Subtle background color
222/// * Odd data rows (1, 3, 5...): Default background
223///
224/// # Arguments
225///
226/// * `row` - The row index to style (headers use `HEADER_ROW` constant)
227/// * `_col` - The column index (unused, but required by the `StyleFunc` signature)
228///
229/// # Returns
230///
231/// * Bold `Style` for header rows
232/// * `Style` with subtle background for even data rows
233/// * Default `Style` for odd data rows
234///
235/// # Examples
236///
237/// ```rust
238/// use lipgloss_table::{Table, zebra_style};
239///
240/// let mut table = Table::new()
241///     .headers(vec!["Name", "Score", "Grade"])
242///     .row(vec!["Alice", "95", "A"])   // Even row - background color
243///     .row(vec!["Bob", "87", "B"])     // Odd row - default
244///     .row(vec!["Charlie", "92", "A"]) // Even row - background color
245///     .style_func(zebra_style);
246///
247/// println!("{}", table.render());
248/// ```
249#[allow(unknown_lints, clippy::manual_is_multiple_of)]
250pub fn zebra_style(row: i32, _col: usize) -> Style {
251    use lipgloss::color::AdaptiveColor;
252    let table_row_even_bg = AdaptiveColor {
253        Light: "#F9FAFB",
254        Dark: "#1F1F1F",
255    };
256    match row {
257        HEADER_ROW => Style::new().bold(true),
258        _ if row % 2 == 0 => Style::new().background(table_row_even_bg),
259        _ => Style::new(),
260    }
261}
262
263/// A subtle style function that provides minimal, professional-looking table styling.
264///
265/// This function creates a clean, minimal aesthetic using muted colors and subtle
266/// contrast. Headers are bold with high-contrast text, while data rows alternate
267/// between normal and muted text colors. All colors are adaptive to work well
268/// with both light and dark terminal themes.
269///
270/// # Row Pattern
271///
272/// * Header row: Bold text with high-contrast color
273/// * Even data rows (0, 2, 4...): Muted text color
274/// * Odd data rows (1, 3, 5...): Normal text color
275///
276/// # Arguments
277///
278/// * `row` - The row index to style (headers use `HEADER_ROW` constant)
279/// * `_col` - The column index (unused, but required by the `StyleFunc` signature)
280///
281/// # Returns
282///
283/// * Bold `Style` with high-contrast foreground for headers
284/// * `Style` with muted foreground for even data rows
285/// * `Style` with normal foreground for odd data rows
286///
287/// # Examples
288///
289/// ```rust
290/// use lipgloss_table::{Table, minimal_style};
291///
292/// let mut table = Table::new()
293///     .headers(vec!["Status", "Task", "Priority"])
294///     .row(vec!["Done", "Fix bug #123", "High"])
295///     .row(vec!["In Progress", "Add new feature", "Medium"])
296///     .row(vec!["Todo", "Update docs", "Low"])
297///     .style_func(minimal_style);
298///
299/// println!("{}", table.render());
300/// ```
301#[allow(unknown_lints, clippy::manual_is_multiple_of)]
302pub fn minimal_style(row: i32, _col: usize) -> Style {
303    use lipgloss::color::AdaptiveColor;
304    let table_header_text = AdaptiveColor {
305        Light: "#171717",
306        Dark: "#F5F5F5",
307    };
308    let table_row_text = AdaptiveColor {
309        Light: "#262626",
310        Dark: "#FAFAFA",
311    };
312    let text_muted = AdaptiveColor {
313        Light: "#737373",
314        Dark: "#A3A3A3",
315    };
316    match row {
317        HEADER_ROW => Style::new().bold(true).foreground(table_header_text),
318        _ if row % 2 == 0 => Style::new().foreground(text_muted),
319        _ => Style::new().foreground(table_row_text),
320    }
321}
322
323/// Creates a style function that applies column-specific styling to table cells.
324///
325/// This function factory generates a style function that can apply different styles
326/// to specific columns while maintaining consistent header styling. It's particularly
327/// useful for highlighting important columns like status indicators, priority levels,
328/// or key data fields.
329///
330/// # Arguments
331///
332/// * `column_styles` - A vector of tuples where each tuple contains:
333///   - `usize`: The zero-based column index to style
334///   - `Style`: The lipgloss style to apply to that column
335///
336/// # Returns
337///
338/// A closure that implements the `StyleFunc` signature, applying:
339/// * Bold styling to all header row cells
340/// * Column-specific styles to matching data cells
341/// * Default styling to other cells
342///
343/// # Examples
344///
345/// ```rust
346/// use lipgloss_table::{Table, column_style_func};
347/// use lipgloss::{Style, Color};
348///
349/// // Define styles for specific columns
350/// let column_styles = vec![
351///     (0, Style::new().foreground(Color::from("#00FF00"))), // Green for first column
352///     (2, Style::new().bold(true).foreground(Color::from("#FF0000"))), // Bold red for third column
353/// ];
354///
355/// let mut table = Table::new()
356///     .headers(vec!["Status", "Task", "Priority", "Assignee"])
357///     .row(vec!["Active", "Fix bug", "High", "Alice"])
358///     .row(vec!["Done", "Add feature", "Medium", "Bob"])
359///     .style_func_boxed(Box::new(column_style_func(column_styles)));
360///
361/// println!("{}", table.render());
362/// ```
363pub fn column_style_func(column_styles: Vec<(usize, Style)>) -> impl Fn(i32, usize) -> Style {
364    move |row: i32, col: usize| {
365        // Apply header styling
366        let mut base_style = if row == HEADER_ROW {
367            Style::new().bold(true)
368        } else {
369            Style::new()
370        };
371
372        // Apply column-specific styling
373        for &(target_col, ref style) in &column_styles {
374            if col == target_col {
375                // Inherit from the column style
376                base_style = base_style.inherit(style.clone());
377                break;
378            }
379        }
380
381        base_style
382    }
383}
384
385/// A trait object type for flexible style functions that can capture their environment.
386///
387/// This type allows for more complex styling logic that can capture variables
388/// from the surrounding scope, unlike the simple function pointer `StyleFunc`.
389/// It's particularly useful when you need to reference external data or state
390/// in your styling logic.
391///
392/// # Examples
393///
394/// ```rust
395/// use lipgloss_table::{Table, BoxedStyleFunc, HEADER_ROW};
396/// use lipgloss::{Style, Color};
397///
398/// let error_color = Color::from("#FF0000");
399/// let warning_color = Color::from("#FFAA00");
400///
401/// let boxed_style: BoxedStyleFunc = Box::new(move |row: i32, col: usize| {
402///     match (row, col) {
403///         (HEADER_ROW, _) => Style::new().bold(true),
404///         (_, 0) => Style::new().foreground(error_color.clone()),
405///         (_, 1) => Style::new().foreground(warning_color.clone()),
406///         _ => Style::new(),
407///     }
408/// });
409/// ```
410pub type BoxedStyleFunc = Box<dyn Fn(i32, usize) -> Style + Send + Sync>;
411
412/// A flexible table renderer with advanced styling and layout capabilities.
413///
414/// `Table` provides a comprehensive solution for rendering tabular data in terminal
415/// applications. It supports a wide range of customization options including borders,
416/// styling functions, width/height constraints, text wrapping, and scrolling.
417///
418/// # Features
419///
420/// - **Flexible Content**: Supports headers, multiple data rows, and various data sources
421/// - **Advanced Styling**: Cell-by-cell styling with function-based or closure-based approaches
422/// - **Border Control**: Granular control over all border elements (top, bottom, sides, separators)
423/// - **Layout Management**: Width/height constraints with automatic wrapping and truncation
424/// - **Scrolling Support**: Offset-based scrolling for large datasets
425/// - **ANSI-Aware**: Proper handling of ANSI escape sequences in cell content
426/// - **Memory Safe**: Built-in protections against excessive memory usage
427///
428/// # Builder Pattern
429///
430/// `Table` uses a fluent builder pattern where each method returns `Self`, allowing
431/// for method chaining. Call `render()` to generate the final string representation.
432///
433/// # Examples
434///
435/// ## Basic Table
436///
437/// ```rust
438/// use lipgloss_table::Table;
439///
440/// let mut table = Table::new()
441///     .headers(vec!["Name", "Age", "City"])
442///     .row(vec!["Alice", "30", "New York"])
443///     .row(vec!["Bob", "25", "London"]);
444///
445/// println!("{}", table.render());
446/// ```
447///
448/// ## Styled Table with Width Constraint
449///
450/// ```rust
451/// use lipgloss_table::{Table, zebra_style};
452/// use lipgloss::rounded_border;
453///
454/// let mut table = Table::new()
455///     .headers(vec!["Product", "Description", "Price"])
456///     .rows(vec![
457///         vec!["Widget A", "A useful widget for all your needs", "$19.99"],
458///         vec!["Widget B", "An even more useful widget", "$29.99"],
459///     ])
460///     .width(50)
461///     .border(rounded_border())
462///     .style_func(zebra_style);
463///
464/// println!("{}", table.render());
465/// ```
466///
467/// ## Scrollable Table with Height Limit
468///
469/// ```rust
470/// use lipgloss_table::Table;
471///
472/// let mut large_table = Table::new()
473///     .headers(vec!["ID", "Data"])
474///     .height(10)  // Limit to 10 lines total
475///     .offset(20); // Start from row 20 (scrolling)
476///
477/// // Add many rows...
478/// for i in 1..=1000 {
479///     large_table = large_table.row(vec![i.to_string(), format!("Data {}", i)]);
480/// }
481///
482/// println!("{}", large_table.render());
483/// println!("Actual height: {}", large_table.compute_height());
484/// ```
485pub struct Table {
486    style_func: StyleFunc,
487    boxed_style_func: Option<BoxedStyleFunc>,
488    border: Border,
489
490    border_top: bool,
491    border_bottom: bool,
492    border_left: bool,
493    border_right: bool,
494    border_header: bool,
495    border_column: bool,
496    border_row: bool,
497
498    border_style: Style,
499    headers: Vec<String>,
500    data: Box<dyn Data>,
501
502    width: i32,
503    height: i32,
504    use_manual_height: bool,
505    offset: usize,
506    wrap: bool,
507
508    // widths tracks the width of each column.
509    widths: Vec<usize>,
510
511    // heights tracks the height of each row.
512    heights: Vec<usize>,
513}
514
515impl Table {
516    /// Creates a new `Table` with default settings and no content.
517    ///
518    /// The default table configuration includes:
519    /// - Rounded borders (`lipgloss::rounded_border()`)
520    /// - All border sides enabled (top, bottom, left, right, header separator, column separators)
521    /// - Row separators disabled
522    /// - Text wrapping enabled
523    /// - No width or height constraints
524    /// - No content (headers or data rows)
525    /// - Basic styling function (`default_styles`)
526    ///
527    /// # Returns
528    ///
529    /// A new `Table` instance ready for configuration via the builder pattern.
530    ///
531    /// # Examples
532    ///
533    /// ```rust
534    /// use lipgloss_table::Table;
535    ///
536    /// let table = Table::new();
537    /// assert_eq!(table.compute_height(), 2); // Just top and bottom borders
538    /// ```
539    ///
540    /// ```rust
541    /// use lipgloss_table::Table;
542    ///
543    /// let mut table = Table::new()
544    ///     .headers(vec!["Column 1", "Column 2"])
545    ///     .row(vec!["Data 1", "Data 2"]);
546    ///
547    /// println!("{}", table.render());
548    /// ```
549    pub fn new() -> Self {
550        Self {
551            style_func: default_styles,
552            boxed_style_func: None,
553            border: lipgloss::rounded_border(),
554            border_bottom: true,
555            border_column: true,
556            border_header: true,
557            border_left: true,
558            border_right: true,
559            border_top: true,
560            border_row: false,
561            border_style: Style::new(),
562            headers: Vec::new(),
563            data: Box::new(StringData::empty()),
564            width: 0,
565            height: 0,
566            use_manual_height: false,
567            offset: 0,
568            wrap: true,
569            widths: Vec::new(),
570            heights: Vec::new(),
571        }
572    }
573
574    /// Removes all data rows from the table while preserving headers and settings.
575    ///
576    /// This method clears only the table's data content, leaving headers, styling,
577    /// borders, and other configuration unchanged. It's useful for reusing a
578    /// configured table with different data.
579    ///
580    /// # Returns
581    ///
582    /// The `Table` instance with all data rows removed, enabling method chaining.
583    ///
584    /// # Examples
585    ///
586    /// ```rust
587    /// use lipgloss_table::Table;
588    ///
589    /// let mut table = Table::new()
590    ///     .headers(vec!["Name", "Age"])
591    ///     .row(vec!["Alice", "30"])
592    ///     .row(vec!["Bob", "25"])
593    ///     .clear_rows()
594    ///     .row(vec!["Charlie", "35"]);
595    ///
596    /// // Table now has headers and only Charlie's row
597    /// println!("{}", table.render());
598    /// ```
599    pub fn clear_rows(mut self) -> Self {
600        self.data = Box::new(StringData::empty());
601        self
602    }
603
604    /// Sets a simple function-based styling function for table cells.
605    ///
606    /// This method accepts a function pointer that determines the style for each
607    /// cell based on its row and column position. The function receives the row
608    /// index (with `HEADER_ROW` for headers) and column index, returning a
609    /// `Style` to apply to that cell.
610    ///
611    /// Using this method will clear any previously set boxed style function.
612    ///
613    /// # Arguments
614    ///
615    /// * `style` - A function that takes `(row: i32, col: usize) -> Style`
616    ///
617    /// # Returns
618    ///
619    /// The `Table` instance with the style function applied, enabling method chaining.
620    ///
621    /// # Examples
622    ///
623    /// ```rust
624    /// use lipgloss_table::{Table, HEADER_ROW, header_row_style};
625    /// use lipgloss::{Style, Color};
626    ///
627    /// // Using a predefined style function
628    /// let table1 = Table::new()
629    ///     .headers(vec!["Name", "Age"])
630    ///     .style_func(header_row_style);
631    ///
632    /// // Using a custom style function
633    /// let custom_style = |row: i32, col: usize| {
634    ///     match (row, col) {
635    ///         (HEADER_ROW, _) => Style::new().bold(true),
636    ///         (_, 0) => Style::new().foreground(Color::from("#00FF00")),
637    ///         _ => Style::new(),
638    ///     }
639    /// };
640    ///
641    /// let table2 = Table::new()
642    ///     .headers(vec!["Status", "Message"])
643    ///     .style_func(custom_style);
644    /// ```
645    pub fn style_func(mut self, style: StyleFunc) -> Self {
646        self.style_func = style;
647        self.boxed_style_func = None; // Clear any boxed style func
648        self
649    }
650
651    /// Sets a flexible closure-based styling function that can capture variables from its environment.
652    ///
653    /// This method allows for more complex styling logic than `style_func` by accepting
654    /// a closure that can capture variables from the surrounding scope. This is useful
655    /// when your styling logic needs to reference external data, configuration, or state.
656    ///
657    /// The closure is boxed and stored, allowing it to outlive the current scope while
658    /// maintaining access to captured variables.
659    ///
660    /// # Type Parameters
661    ///
662    /// * `F` - A closure type that implements `Fn(i32, usize) -> Style + Send + Sync + 'static`
663    ///
664    /// # Arguments
665    ///
666    /// * `style` - A closure that takes `(row: i32, col: usize) -> Style`
667    ///
668    /// # Returns
669    ///
670    /// The `Table` instance with the boxed style function applied, enabling method chaining.
671    ///
672    /// # Examples
673    ///
674    /// ```rust
675    /// use lipgloss_table::{Table, HEADER_ROW};
676    /// use lipgloss::{Style, Color};
677    ///
678    /// // Capture colors from the environment
679    /// let error_color = Color::from("#FF0000");
680    /// let success_color = Color::from("#00FF00");
681    /// let warning_color = Color::from("#FFAA00");
682    ///
683    /// let mut table = Table::new()
684    ///     .headers(vec!["Status", "Message", "Code"])
685    ///     .row(vec!["Error", "Something failed", "500"])
686    ///     .row(vec!["Success", "All good", "200"])
687    ///     .row(vec!["Warning", "Be careful", "400"])
688    ///     .style_func_boxed(move |row: i32, col: usize| {
689    ///         match (row, col) {
690    ///             (HEADER_ROW, _) => Style::new().bold(true),
691    ///             (_, 0) => {
692    ///                 // Style status column based on content
693    ///                 match row {
694    ///                     0 => Style::new().foreground(error_color.clone()),
695    ///                     1 => Style::new().foreground(success_color.clone()),
696    ///                     2 => Style::new().foreground(warning_color.clone()),
697    ///                     _ => Style::new(),
698    ///                 }
699    ///             }
700    ///             _ => Style::new(),
701    ///         }
702    ///     });
703    ///
704    /// println!("{}", table.render());
705    /// ```
706    pub fn style_func_boxed<F>(mut self, style: F) -> Self
707    where
708        F: Fn(i32, usize) -> Style + Send + Sync + 'static,
709    {
710        self.boxed_style_func = Some(Box::new(style));
711        self
712    }
713
714    /// Sets the table border.
715    pub fn border(mut self, border: Border) -> Self {
716        self.border = border;
717        self
718    }
719
720    /// Sets the style for the table border.
721    pub fn border_style(mut self, style: Style) -> Self {
722        self.border_style = style;
723        self
724    }
725
726    /// Sets whether or not the top border is rendered.
727    pub fn border_top(mut self, v: bool) -> Self {
728        self.border_top = v;
729        self
730    }
731
732    /// Sets whether or not the bottom border is rendered.
733    pub fn border_bottom(mut self, v: bool) -> Self {
734        self.border_bottom = v;
735        self
736    }
737
738    /// Sets whether or not the left border is rendered.
739    pub fn border_left(mut self, v: bool) -> Self {
740        self.border_left = v;
741        self
742    }
743
744    /// Sets whether or not the right border is rendered.
745    pub fn border_right(mut self, v: bool) -> Self {
746        self.border_right = v;
747        self
748    }
749
750    /// Sets whether or not the header separator is rendered.
751    pub fn border_header(mut self, v: bool) -> Self {
752        self.border_header = v;
753        self
754    }
755
756    /// Sets whether or not column separators are rendered.
757    pub fn border_column(mut self, v: bool) -> Self {
758        self.border_column = v;
759        self
760    }
761
762    /// Sets whether or not row separators are rendered.
763    pub fn border_row(mut self, v: bool) -> Self {
764        self.border_row = v;
765        self
766    }
767
768    /// Sets the column headers for the table.
769    ///
770    /// Headers are displayed at the top of the table and are typically styled
771    /// differently from data rows (e.g., bold text). The number of headers
772    /// determines the number of columns in the table.
773    ///
774    /// # Type Parameters
775    ///
776    /// * `I` - An iterator type that yields items convertible to `String`
777    /// * `S` - A type that can be converted into `String`
778    ///
779    /// # Arguments
780    ///
781    /// * `headers` - An iterable collection of header values (strings, string slices, etc.)
782    ///
783    /// # Returns
784    ///
785    /// The `Table` instance with headers set, enabling method chaining.
786    ///
787    /// # Examples
788    ///
789    /// ```rust
790    /// use lipgloss_table::Table;
791    ///
792    /// // Using string slices
793    /// let table1 = Table::new()
794    ///     .headers(vec!["Name", "Age", "City"]);
795    ///
796    /// // Using owned strings
797    /// let headers = vec!["ID".to_string(), "Description".to_string()];
798    /// let table2 = Table::new()
799    ///     .headers(headers);
800    ///
801    /// // Using an array
802    /// let table3 = Table::new()
803    ///     .headers(["Product", "Price", "Stock"]);
804    /// ```
805    pub fn headers<I, S>(mut self, headers: I) -> Self
806    where
807        I: IntoIterator<Item = S>,
808        S: Into<String>,
809    {
810        self.headers = headers.into_iter().map(|s| s.into()).collect();
811        self
812    }
813
814    /// Adds a single row to the table.
815    pub fn row<I, S>(mut self, row: I) -> Self
816    where
817        I: IntoIterator<Item = S>,
818        S: Into<String>,
819    {
820        let row_data: Vec<String> = row.into_iter().map(|s| s.into()).collect();
821
822        // Convert current data to StringData - always create a new one from the matrix
823        let matrix = data_to_matrix(self.data.as_ref());
824        let mut string_data = StringData::new(matrix);
825        string_data.append(row_data);
826        self.data = Box::new(string_data);
827        self
828    }
829
830    /// Adds multiple rows to the table.
831    pub fn rows<I, J, S>(mut self, rows: I) -> Self
832    where
833        I: IntoIterator<Item = J>,
834        J: IntoIterator<Item = S>,
835        S: Into<String>,
836    {
837        for row in rows {
838            self = self.row(row);
839        }
840        self
841    }
842
843    /// Sets the data source for the table.
844    pub fn data<D: Data + 'static>(mut self, data: D) -> Self {
845        self.data = Box::new(data);
846        self
847    }
848
849    /// Sets a fixed width for the table.
850    pub fn width(mut self, w: i32) -> Self {
851        self.width = w;
852        self
853    }
854
855    /// Sets a fixed height for the table.
856    pub fn height(mut self, h: i32) -> Self {
857        self.height = h;
858        self.use_manual_height = h > 0;
859        self
860    }
861
862    /// Sets the row offset for the table (for scrolling).
863    pub fn offset(mut self, o: usize) -> Self {
864        self.offset = o;
865        self
866    }
867
868    /// Sets whether text wrapping is enabled.
869    pub fn wrap(mut self, w: bool) -> Self {
870        self.wrap = w;
871        self
872    }
873
874    /// Renders the table to a complete string representation.
875    ///
876    /// This method performs the final rendering step, calculating layout dimensions,
877    /// applying styles, and constructing the complete table string with borders,
878    /// headers, and data rows. It must be called to generate the visual output.
879    ///
880    /// The rendering process includes:
881    /// - Calculating optimal column widths and row heights
882    /// - Applying cell styles and text wrapping/truncation
883    /// - Constructing borders and separators
884    /// - Handling height constraints and overflow indicators
885    ///
886    /// # Returns
887    ///
888    /// A `String` containing the complete rendered table with ANSI escape sequences
889    /// for styling and proper spacing.
890    ///
891    /// # Examples
892    ///
893    /// ```rust
894    /// use lipgloss_table::{Table, header_row_style};
895    ///
896    /// let mut table = Table::new()
897    ///     .headers(vec!["Name", "Score"])
898    ///     .row(vec!["Alice", "95"])
899    ///     .row(vec!["Bob", "87"])
900    ///     .style_func(header_row_style);
901    ///
902    /// let output = table.render();
903    /// println!("{}", output);
904    /// ```
905    ///
906    /// ```rust
907    /// use lipgloss_table::Table;
908    ///
909    /// let mut table = Table::new()
910    ///     .headers(vec!["Product", "Description"])
911    ///     .row(vec!["Widget", "A very long description that will wrap"])
912    ///     .width(30);
913    ///
914    /// let output = table.render();
915    /// // Output will be wrapped to fit within 30 characters width
916    /// println!("{}", output);
917    /// ```
918    pub fn render(&mut self) -> String {
919        self.resize();
920        self.construct_table()
921    }
922
923    /// Computes the total height the table will occupy when rendered.
924    ///
925    /// This method calculates the exact number of terminal lines the table will
926    /// use when rendered, including all borders, headers, data rows, and separators.
927    /// It's useful for layout planning, especially when working with height-constrained
928    /// terminals or when implementing scrolling interfaces.
929    ///
930    /// The calculation includes:
931    /// - Top and bottom borders (if enabled)
932    /// - Header row and header separator (if headers exist)
933    /// - All data rows with their calculated heights
934    /// - Row separators between data rows (if enabled)
935    ///
936    /// # Returns
937    ///
938    /// The total height in terminal lines as a `usize`.
939    ///
940    /// # Examples
941    ///
942    /// ```rust
943    /// use lipgloss_table::Table;
944    ///
945    /// let table = Table::new();
946    /// assert_eq!(table.compute_height(), 2); // Just top and bottom borders
947    ///
948    /// let table_with_content = Table::new()
949    ///     .headers(vec!["Name", "Age"])
950    ///     .row(vec!["Alice", "30"]);
951    /// // Height = top border + header + header separator + data row + bottom border
952    /// assert_eq!(table_with_content.compute_height(), 5);
953    /// ```
954    ///
955    /// ```rust
956    /// use lipgloss_table::Table;
957    ///
958    /// let mut large_table = Table::new()
959    ///     .headers(vec!["ID", "Data"])
960    ///     .height(10); // Height constraint
961    ///
962    /// for i in 1..=100 {
963    ///     large_table = large_table.row(vec![i.to_string(), format!("Data {}", i)]);
964    /// }
965    ///
966    /// large_table.render(); // Must render first to populate heights
967    /// let height = large_table.compute_height();
968    /// // compute_height() returns the natural height, not constrained height
969    /// // The actual rendered output will be constrained to 10 lines
970    /// assert!(height > 10); // Natural height is larger than constraint
971    /// ```
972    pub fn compute_height(&self) -> usize {
973        let has_headers = !self.headers.is_empty();
974        let data_rows = self.data.rows();
975
976        // If no rows and no headers, just border height
977        if data_rows == 0 && !has_headers {
978            return if self.border_top && self.border_bottom {
979                2
980            } else if self.border_top || self.border_bottom {
981                1
982            } else {
983                0
984            };
985        }
986
987        let mut total_height = 0;
988
989        // Top border
990        if self.border_top {
991            total_height += 1;
992        }
993
994        // Header row
995        if has_headers {
996            total_height += 1;
997
998            // Header separator
999            if self.border_header {
1000                total_height += 1;
1001            }
1002        }
1003
1004        // Data rows
1005        if data_rows > 0 {
1006            // Sum the heights of all data rows
1007            let header_offset = if has_headers { 1 } else { 0 };
1008            for i in 0..data_rows {
1009                let row_height = self.heights.get(i + header_offset).unwrap_or(&1);
1010                total_height += row_height;
1011
1012                // Row separators (between data rows, not after the last one)
1013                if self.border_row && i < data_rows - 1 {
1014                    total_height += 1;
1015                }
1016            }
1017        }
1018
1019        // Bottom border
1020        if self.border_bottom {
1021            total_height += 1;
1022        }
1023
1024        total_height
1025    }
1026
1027    // Private methods for internal rendering
1028
1029    /// Get the appropriate style for a cell, using either the function pointer or boxed function.
1030    fn get_cell_style(&self, row: i32, col: usize) -> Style {
1031        if let Some(ref boxed_func) = self.boxed_style_func {
1032            boxed_func(row, col)
1033        } else {
1034            (self.style_func)(row, col)
1035        }
1036    }
1037
1038    fn resize(&mut self) {
1039        let has_headers = !self.headers.is_empty();
1040        let rows = data_to_matrix(self.data.as_ref());
1041        let mut resizer = Resizer::new(self.width, self.height, self.headers.clone(), rows);
1042        resizer.wrap = self.wrap;
1043        resizer.border_column = self.border_column;
1044        resizer.y_paddings = vec![vec![0; resizer.columns.len()]; resizer.all_rows.len()];
1045
1046        // Calculate style-based padding for each cell
1047        resizer.row_heights = resizer.default_row_heights();
1048
1049        for (i, row) in resizer.all_rows.iter().enumerate() {
1050            if i >= resizer.y_paddings.len() {
1051                resizer.y_paddings.push(vec![0; row.len()]);
1052            }
1053            if resizer.y_paddings[i].len() < row.len() {
1054                resizer.y_paddings[i].resize(row.len(), 0);
1055            }
1056
1057            for j in 0..row.len() {
1058                if j >= resizer.columns.len() {
1059                    continue;
1060                }
1061
1062                // Making sure we're passing the right index to the style function.
1063                // The header row should be `-1` and the others should start from `0`.
1064                let row_index = if has_headers { i as i32 - 1 } else { i as i32 };
1065                let style = self.get_cell_style(row_index, j);
1066
1067                // Extract margin and padding values
1068                let (top_margin, right_margin, bottom_margin, left_margin) = (
1069                    style.get_margin_top().max(0) as usize,
1070                    style.get_margin_right().max(0) as usize,
1071                    style.get_margin_bottom().max(0) as usize,
1072                    style.get_margin_left().max(0) as usize,
1073                );
1074                let (top_padding, right_padding, bottom_padding, left_padding) = (
1075                    style.get_padding_top().max(0) as usize,
1076                    style.get_padding_right().max(0) as usize,
1077                    style.get_padding_bottom().max(0) as usize,
1078                    style.get_padding_left().max(0) as usize,
1079                );
1080
1081                let total_horizontal_padding =
1082                    left_margin + right_margin + left_padding + right_padding;
1083                resizer.columns[j].x_padding =
1084                    resizer.columns[j].x_padding.max(total_horizontal_padding);
1085
1086                let width = style.get_width();
1087                if width > 0 {
1088                    resizer.columns[j].fixed_width =
1089                        resizer.columns[j].fixed_width.max(width as usize);
1090                }
1091
1092                let height = style.get_height();
1093                if height > 0 {
1094                    resizer.row_heights[i] = resizer.row_heights[i].max(height as usize);
1095                }
1096
1097                let total_vertical_padding =
1098                    top_margin + bottom_margin + top_padding + bottom_padding;
1099                resizer.y_paddings[i][j] = total_vertical_padding;
1100            }
1101        }
1102
1103        // Auto-detect table width if not specified
1104        if resizer.table_width <= 0 {
1105            resizer.table_width = resizer.detect_table_width();
1106        }
1107
1108        let (widths, heights) = resizer.optimized_widths();
1109        self.widths = widths;
1110        self.heights = heights;
1111    }
1112
1113    fn construct_table(&self) -> String {
1114        let mut result = String::new();
1115        let has_headers = !self.headers.is_empty();
1116        let _data_rows = self.data.rows();
1117
1118        if self.widths.is_empty() {
1119            return result;
1120        }
1121
1122        // Construct top border
1123        if self.border_top {
1124            result.push_str(&self.construct_top_border());
1125            result.push('\n');
1126        }
1127
1128        // Construct headers
1129        if has_headers {
1130            result.push_str(&self.construct_headers());
1131            result.push('\n');
1132
1133            // Header separator
1134            if self.border_header {
1135                result.push_str(&self.construct_header_separator());
1136                result.push('\n');
1137            }
1138        }
1139
1140        // Construct data rows
1141        let available_lines = if self.use_manual_height && self.height > 0 {
1142            let used_lines = if self.border_top { 1 } else { 0 }
1143                + if has_headers { 1 } else { 0 }
1144                + if has_headers && self.border_header {
1145                    1
1146                } else {
1147                    0
1148                }
1149                + if self.border_bottom { 1 } else { 0 };
1150            (self.height as usize).saturating_sub(used_lines)
1151        } else {
1152            usize::MAX
1153        };
1154
1155        result.push_str(&self.construct_rows(available_lines));
1156
1157        // Construct bottom border
1158        if self.border_bottom {
1159            if !result.is_empty() && !result.ends_with('\n') {
1160                result.push('\n');
1161            }
1162            result.push_str(&self.construct_bottom_border());
1163        }
1164
1165        result
1166    }
1167
1168    fn construct_top_border(&self) -> String {
1169        let mut border_parts = Vec::new();
1170
1171        if self.border_left {
1172            border_parts.push(self.border.top_left.to_string());
1173        }
1174
1175        for (i, &width) in self.widths.iter().enumerate() {
1176            border_parts.push(safe_str_repeat(self.border.top, width));
1177
1178            if i < self.widths.len() - 1 && self.border_column {
1179                border_parts.push(self.border.middle_top.to_string());
1180            }
1181        }
1182
1183        if self.border_right {
1184            border_parts.push(self.border.top_right.to_string());
1185        }
1186
1187        self.border_style.render(&border_parts.join(""))
1188    }
1189
1190    fn construct_bottom_border(&self) -> String {
1191        let mut border_parts = Vec::new();
1192
1193        if self.border_left {
1194            border_parts.push(self.border.bottom_left.to_string());
1195        }
1196
1197        for (i, &width) in self.widths.iter().enumerate() {
1198            border_parts.push(safe_str_repeat(self.border.bottom, width));
1199
1200            if i < self.widths.len() - 1 && self.border_column {
1201                border_parts.push(self.border.middle_bottom.to_string());
1202            }
1203        }
1204
1205        if self.border_right {
1206            border_parts.push(self.border.bottom_right.to_string());
1207        }
1208
1209        self.border_style.render(&border_parts.join(""))
1210    }
1211
1212    fn construct_header_separator(&self) -> String {
1213        let mut border_parts = Vec::new();
1214
1215        if self.border_left {
1216            border_parts.push(self.border.middle_left.to_string());
1217        }
1218
1219        for (i, &width) in self.widths.iter().enumerate() {
1220            border_parts.push(safe_str_repeat(self.border.top, width));
1221
1222            if i < self.widths.len() - 1 && self.border_column {
1223                border_parts.push(self.border.middle.to_string());
1224            }
1225        }
1226
1227        if self.border_right {
1228            border_parts.push(self.border.middle_right.to_string());
1229        }
1230
1231        self.border_style.render(&border_parts.join(""))
1232    }
1233
1234    fn construct_headers(&self) -> String {
1235        self.construct_row_content(&self.headers, HEADER_ROW)
1236    }
1237
1238    fn construct_rows(&self, available_lines: usize) -> String {
1239        let mut result = String::new();
1240        let mut lines_used = 0;
1241        let data_rows = self.data.rows();
1242
1243        for i in self.offset..data_rows {
1244            if lines_used >= available_lines {
1245                // Add overflow indicator if we have more data
1246                if i < data_rows {
1247                    result.push_str(&self.construct_overflow_row());
1248                }
1249                break;
1250            }
1251
1252            // Get row data
1253            let mut row_data = Vec::new();
1254            for j in 0..self.data.columns() {
1255                row_data.push(self.data.at(i, j));
1256            }
1257
1258            result.push_str(&self.construct_row_content(&row_data, i as i32));
1259            lines_used += self
1260                .heights
1261                .get(i + if !self.headers.is_empty() { 1 } else { 0 })
1262                .unwrap_or(&1);
1263
1264            // Add row separator if needed
1265            if self.border_row && i < data_rows - 1 && lines_used < available_lines {
1266                result.push('\n');
1267                result.push_str(&self.construct_row_separator());
1268                lines_used += 1;
1269            }
1270
1271            if i < data_rows - 1 {
1272                result.push('\n');
1273            }
1274        }
1275
1276        result
1277    }
1278
1279    fn construct_row_content(&self, row_data: &[String], row_index: i32) -> String {
1280        let mut cell_parts = Vec::new();
1281
1282        if self.border_left {
1283            cell_parts.push(self.border.left.to_string());
1284        }
1285
1286        for (j, cell_content) in row_data.iter().enumerate() {
1287            if j >= self.widths.len() {
1288                break;
1289            }
1290
1291            let cell_width = self.widths[j];
1292            let style = self.get_cell_style(row_index, j);
1293
1294            // Apply cell styling and fit to width
1295            let styled_content = self.style_cell_content(cell_content, cell_width, style);
1296            cell_parts.push(styled_content);
1297
1298            if self.border_column && j < row_data.len() - 1 {
1299                cell_parts.push(self.border.left.to_string());
1300            }
1301        }
1302
1303        if self.border_right {
1304            cell_parts.push(self.border.right.to_string());
1305        }
1306
1307        cell_parts.join("")
1308    }
1309
1310    fn construct_row_separator(&self) -> String {
1311        let mut border_parts = Vec::new();
1312
1313        if self.border_left {
1314            border_parts.push(self.border.middle_left.to_string());
1315        }
1316
1317        for (i, &width) in self.widths.iter().enumerate() {
1318            border_parts.push(safe_str_repeat(self.border.top, width));
1319
1320            if i < self.widths.len() - 1 && self.border_column {
1321                border_parts.push(self.border.middle.to_string());
1322            }
1323        }
1324
1325        if self.border_right {
1326            border_parts.push(self.border.middle_right.to_string());
1327        }
1328
1329        self.border_style.render(&border_parts.join(""))
1330    }
1331
1332    fn construct_overflow_row(&self) -> String {
1333        let mut cell_parts = Vec::new();
1334
1335        if self.border_left {
1336            cell_parts.push(self.border.left.to_string());
1337        }
1338
1339        for (i, &width) in self.widths.iter().enumerate() {
1340            let ellipsis = "…".to_string();
1341            let padding = safe_repeat(' ', width.saturating_sub(ellipsis.len()));
1342            cell_parts.push(format!("{}{}", ellipsis, padding));
1343
1344            if self.border_column && i < self.widths.len() - 1 {
1345                cell_parts.push(self.border.left.to_string());
1346            }
1347        }
1348
1349        if self.border_right {
1350            cell_parts.push(self.border.right.to_string());
1351        }
1352
1353        cell_parts.join("")
1354    }
1355
1356    fn style_cell_content(&self, content: &str, width: usize, style: Style) -> String {
1357        // Handle content wrapping if needed
1358        let fitted_content = if self.wrap {
1359            self.wrap_cell_content(content, width)
1360        } else {
1361            self.truncate_cell_content(content, width)
1362        };
1363
1364        // Apply the lipgloss style to the content
1365        // The style should handle its own width constraints, so we apply it directly
1366        style.width(width as i32).render(&fitted_content)
1367    }
1368
1369    fn truncate_cell_content(&self, content: &str, width: usize) -> String {
1370        let content_width = lipgloss::width(content);
1371
1372        if content_width > width {
1373            // Truncate with ellipsis, handling ANSI sequences properly
1374            if width == 0 {
1375                return String::new();
1376            } else if width == 1 {
1377                return "…".to_string();
1378            }
1379
1380            // For ANSI-aware truncation, we need to be more careful
1381            // For now, use a simple approach that may not be perfect with ANSI sequences
1382            let chars: Vec<char> = content.chars().collect();
1383            let mut result = String::new();
1384            let mut current_width = 0;
1385
1386            for ch in chars {
1387                let char_str = ch.to_string();
1388                let char_width = lipgloss::width(&char_str);
1389
1390                if current_width + char_width + 1 > width {
1391                    // +1 for ellipsis
1392                    break;
1393                }
1394
1395                result.push(ch);
1396                current_width += char_width;
1397            }
1398
1399            result.push('…');
1400            result
1401        } else {
1402            content.to_string()
1403        }
1404    }
1405
1406    fn wrap_cell_content(&self, content: &str, width: usize) -> String {
1407        if width == 0 {
1408            return String::new();
1409        }
1410
1411        let mut wrapped_lines = Vec::new();
1412
1413        // Handle existing line breaks
1414        for line in content.lines() {
1415            if line.is_empty() {
1416                wrapped_lines.push(String::new());
1417                continue;
1418            }
1419
1420            // Use lipgloss width which handles ANSI sequences
1421            let line_width = lipgloss::width(line);
1422            if line_width <= width {
1423                wrapped_lines.push(line.to_string());
1424            } else {
1425                // Need to wrap this line - use ANSI-aware wrapping
1426                wrapped_lines.extend(self.wrap_line_ansi_aware(line, width));
1427            }
1428        }
1429
1430        wrapped_lines.join("\n")
1431    }
1432
1433    fn wrap_line_ansi_aware(&self, line: &str, width: usize) -> Vec<String> {
1434        // For now, use a simple word-based wrapping that preserves ANSI sequences
1435        // This could be enhanced to use lipgloss's word wrapping utilities if available
1436        let words: Vec<&str> = line.split_whitespace().collect();
1437        let mut lines = Vec::new();
1438        let mut current_line = String::new();
1439        let mut current_width = 0;
1440
1441        for word in words {
1442            let word_width = lipgloss::width(word);
1443
1444            // If adding this word would exceed width, start a new line
1445            if !current_line.is_empty() && current_width + 1 + word_width > width {
1446                lines.push(current_line);
1447                current_line = word.to_string();
1448                current_width = word_width;
1449            } else if current_line.is_empty() {
1450                current_line = word.to_string();
1451                current_width = word_width;
1452            } else {
1453                current_line.push(' ');
1454                current_line.push_str(word);
1455                current_width += 1 + word_width;
1456            }
1457        }
1458
1459        if !current_line.is_empty() {
1460            lines.push(current_line);
1461        }
1462
1463        if lines.is_empty() {
1464            lines.push(String::new());
1465        }
1466
1467        lines
1468    }
1469}
1470
1471impl fmt::Display for Table {
1472    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1473        // Need to create a mutable copy for rendering since fmt doesn't allow mutable self
1474        let mut table_copy = Table {
1475            style_func: self.style_func,
1476            boxed_style_func: None, // Cannot clone boxed closures easily
1477            border: self.border,
1478            border_top: self.border_top,
1479            border_bottom: self.border_bottom,
1480            border_left: self.border_left,
1481            border_right: self.border_right,
1482            border_header: self.border_header,
1483            border_column: self.border_column,
1484            border_row: self.border_row,
1485            border_style: self.border_style.clone(),
1486            headers: self.headers.clone(),
1487            data: Box::new(StringData::new(data_to_matrix(self.data.as_ref()))),
1488            width: self.width,
1489            height: self.height,
1490            use_manual_height: self.use_manual_height,
1491            offset: self.offset,
1492            wrap: self.wrap,
1493            widths: self.widths.clone(),
1494            heights: self.heights.clone(),
1495        };
1496
1497        write!(f, "{}", table_copy.render())
1498    }
1499}
1500
1501impl Default for Table {
1502    fn default() -> Self {
1503        Self::new()
1504    }
1505}
1506
1507#[cfg(test)]
1508mod tests {
1509    use super::*;
1510
1511    #[test]
1512    fn test_table_new() {
1513        let table = Table::new();
1514        assert_eq!(table.headers.len(), 0);
1515        assert_eq!(table.data.rows(), 0);
1516        assert_eq!(table.data.columns(), 0);
1517        assert!(table.border_top);
1518        assert!(table.border_bottom);
1519        assert!(table.border_left);
1520        assert!(table.border_right);
1521        assert!(table.border_header);
1522        assert!(table.border_column);
1523        assert!(!table.border_row);
1524        assert!(table.wrap);
1525    }
1526
1527    #[test]
1528    fn test_table_headers() {
1529        let table = Table::new().headers(vec!["Name", "Age", "Location"]);
1530        assert_eq!(table.headers.len(), 3);
1531        assert_eq!(table.headers[0], "Name");
1532        assert_eq!(table.headers[1], "Age");
1533        assert_eq!(table.headers[2], "Location");
1534    }
1535
1536    #[test]
1537    fn test_table_rows() {
1538        let table = Table::new()
1539            .headers(vec!["Name", "Age"])
1540            .row(vec!["Alice", "30"])
1541            .row(vec!["Bob", "25"]);
1542
1543        assert_eq!(table.data.rows(), 2);
1544        assert_eq!(table.data.columns(), 2);
1545        assert_eq!(table.data.at(0, 0), "Alice");
1546        assert_eq!(table.data.at(0, 1), "30");
1547        assert_eq!(table.data.at(1, 0), "Bob");
1548        assert_eq!(table.data.at(1, 1), "25");
1549    }
1550
1551    #[test]
1552    fn test_table_builder_pattern() {
1553        let table = Table::new()
1554            .border_top(false)
1555            .border_bottom(false)
1556            .width(80)
1557            .height(10)
1558            .wrap(false);
1559
1560        assert!(!table.border_top);
1561        assert!(!table.border_bottom);
1562        assert_eq!(table.width, 80);
1563        assert_eq!(table.height, 10);
1564        assert!(!table.wrap);
1565    }
1566
1567    #[test]
1568    fn test_compute_height_empty_table() {
1569        let table = Table::new();
1570        assert_eq!(table.compute_height(), 2); // top + bottom border
1571
1572        let table_no_borders = Table::new().border_top(false).border_bottom(false);
1573        assert_eq!(table_no_borders.compute_height(), 0);
1574
1575        let table_top_only = Table::new().border_bottom(false);
1576        assert_eq!(table_top_only.compute_height(), 1);
1577
1578        let table_bottom_only = Table::new().border_top(false);
1579        assert_eq!(table_bottom_only.compute_height(), 1);
1580    }
1581
1582    #[test]
1583    fn test_compute_height_headers_only() {
1584        let table = Table::new().headers(vec!["Name", "Age"]);
1585        // top border + header + header separator + bottom border
1586        assert_eq!(table.compute_height(), 4);
1587
1588        let table_no_header_sep = Table::new()
1589            .headers(vec!["Name", "Age"])
1590            .border_header(false);
1591        // top border + header + bottom border
1592        assert_eq!(table_no_header_sep.compute_height(), 3);
1593
1594        let table_no_borders = Table::new()
1595            .headers(vec!["Name", "Age"])
1596            .border_top(false)
1597            .border_bottom(false)
1598            .border_header(false);
1599        // just header
1600        assert_eq!(table_no_borders.compute_height(), 1);
1601    }
1602
1603    #[test]
1604    fn test_compute_height_with_data() {
1605        let mut table = Table::new()
1606            .headers(vec!["Name", "Age"])
1607            .row(vec!["Alice", "30"])
1608            .row(vec!["Bob", "25"]);
1609
1610        // Need to render first to populate heights
1611        table.render();
1612
1613        // top border + header + header separator + 2 data rows + bottom border = 6
1614        assert_eq!(table.compute_height(), 6);
1615    }
1616
1617    #[test]
1618    fn test_compute_height_with_row_borders() {
1619        let mut table = Table::new()
1620            .headers(vec!["Name", "Age"])
1621            .row(vec!["Alice", "30"])
1622            .row(vec!["Bob", "25"])
1623            .border_row(true);
1624
1625        table.render();
1626
1627        // top border + header + header separator + row1 + row separator + row2 + bottom border = 7
1628        assert_eq!(table.compute_height(), 7);
1629    }
1630
1631    #[test]
1632    fn test_compute_height_data_only() {
1633        let mut table = Table::new().row(vec!["Alice", "30"]).row(vec!["Bob", "25"]);
1634
1635        table.render();
1636
1637        // top border + 2 data rows + bottom border = 4
1638        assert_eq!(table.compute_height(), 4);
1639
1640        let mut table_with_row_borders = Table::new()
1641            .row(vec!["Alice", "30"])
1642            .row(vec!["Bob", "25"])
1643            .border_row(true);
1644
1645        table_with_row_borders.render();
1646
1647        // top border + row1 + row separator + row2 + bottom border = 5
1648        assert_eq!(table_with_row_borders.compute_height(), 5);
1649    }
1650
1651    #[test]
1652    fn test_compute_height_single_row() {
1653        let mut table = Table::new().headers(vec!["Name"]).row(vec!["Alice"]);
1654
1655        table.render();
1656
1657        // top border + header + header separator + 1 data row + bottom border = 5
1658        assert_eq!(table.compute_height(), 5);
1659    }
1660
1661    #[test]
1662    fn test_compute_height_minimal_borders() {
1663        let mut table = Table::new()
1664            .headers(vec!["Name", "Age"])
1665            .row(vec!["Alice", "30"])
1666            .border_top(false)
1667            .border_bottom(false)
1668            .border_header(false);
1669
1670        table.render();
1671
1672        // just header + data row = 2
1673        assert_eq!(table.compute_height(), 2);
1674    }
1675
1676    #[test]
1677    fn test_table_clear_rows() {
1678        let table = Table::new()
1679            .row(vec!["A", "B"])
1680            .row(vec!["C", "D"])
1681            .clear_rows();
1682
1683        assert_eq!(table.data.rows(), 0);
1684        assert_eq!(table.data.columns(), 0);
1685    }
1686
1687    #[test]
1688    fn test_table_rendering() {
1689        let mut table = Table::new()
1690            .headers(vec!["Name", "Age", "City"])
1691            .row(vec!["Alice", "30", "New York"])
1692            .row(vec!["Bob", "25", "London"]);
1693
1694        let output = table.render();
1695        assert!(!output.is_empty());
1696
1697        // Should contain the header and data
1698        assert!(output.contains("Name"));
1699        assert!(output.contains("Alice"));
1700        assert!(output.contains("Bob"));
1701
1702        // Should have borders by default
1703        assert!(output.contains("┌") || output.contains("╭")); // Top-left corner
1704    }
1705
1706    #[test]
1707    fn test_table_no_borders() {
1708        let mut table = Table::new()
1709            .headers(vec!["Name", "Age"])
1710            .row(vec!["Alice", "30"])
1711            .border_top(false)
1712            .border_bottom(false)
1713            .border_left(false)
1714            .border_right(false)
1715            .border_column(false);
1716
1717        let output = table.render();
1718        assert!(!output.is_empty());
1719        assert!(output.contains("Name"));
1720        assert!(output.contains("Alice"));
1721
1722        // Should not contain border characters
1723        assert!(!output.contains("┌"));
1724        assert!(!output.contains("│"));
1725    }
1726
1727    #[test]
1728    fn test_table_width_constraint() {
1729        let mut table = Table::new()
1730            .headers(vec!["Name", "Age", "City"])
1731            .row(vec!["Alice Johnson", "28", "New York"])
1732            .row(vec!["Bob Smith", "35", "London"])
1733            .width(25); // Force narrow width
1734
1735        let output = table.render();
1736        assert!(!output.is_empty());
1737
1738        // Each line should respect the width constraint (using display width, not character count)
1739        for line in output.lines() {
1740            // Use lipgloss width which handles ANSI sequences properly
1741            let line_width = lipgloss::width(line);
1742            assert!(
1743                line_width <= 25,
1744                "Line '{}' has display width {} > 25",
1745                line,
1746                line_width
1747            );
1748        }
1749    }
1750
1751    #[test]
1752    fn test_comprehensive_table_demo() {
1753        let mut table = Table::new()
1754            .headers(vec!["Name", "Age", "City", "Occupation"])
1755            .row(vec!["Alice Johnson", "28", "New York", "Software Engineer"])
1756            .row(vec!["Bob Smith", "35", "London", "Product Manager"])
1757            .row(vec!["Charlie Brown", "42", "Tokyo", "UX Designer"])
1758            .row(vec!["Diana Prince", "30", "Paris", "Data Scientist"]);
1759
1760        let output = table.render();
1761        println!("\n=== Comprehensive Table Demo ===");
1762        println!("{}", output);
1763
1764        assert!(!output.is_empty());
1765        assert!(output.contains("Alice Johnson"));
1766        assert!(output.contains("Software Engineer"));
1767
1768        // Test different border styles
1769        println!("\n=== No Borders Demo ===");
1770        let mut no_border_table = Table::new()
1771            .headers(vec!["Item", "Price"])
1772            .row(vec!["Coffee", "$3.50"])
1773            .row(vec!["Tea", "$2.25"])
1774            .border_top(false)
1775            .border_bottom(false)
1776            .border_left(false)
1777            .border_right(false)
1778            .border_column(false)
1779            .border_header(false);
1780
1781        println!("{}", no_border_table.render());
1782
1783        // Test width constraint
1784        println!("\n=== Width Constrained Table ===");
1785        let mut narrow_table = Table::new()
1786            .headers(vec!["Product", "Description", "Price"])
1787            .row(vec![
1788                "MacBook Pro",
1789                "Powerful laptop for developers",
1790                "$2399",
1791            ])
1792            .row(vec![
1793                "iPhone",
1794                "Latest smartphone with amazing camera",
1795                "$999",
1796            ])
1797            .width(40);
1798
1799        println!("{}", narrow_table.render());
1800    }
1801
1802    #[test]
1803    fn test_empty_table() {
1804        let mut table = Table::new();
1805        let output = table.render();
1806
1807        // Empty table should produce minimal output
1808        assert!(output.is_empty() || output.trim().is_empty());
1809    }
1810
1811    #[test]
1812    #[allow(unknown_lints, clippy::manual_is_multiple_of)]
1813    fn test_cell_styling_with_lipgloss() {
1814        use lipgloss::{
1815            color::{STATUS_ERROR, TEXT_MUTED},
1816            Style,
1817        };
1818
1819        let style_func = |row: i32, _col: usize| match row {
1820            HEADER_ROW => Style::new().bold(true).foreground(STATUS_ERROR),
1821            _ if row % 2 == 0 => Style::new().foreground(TEXT_MUTED),
1822            _ => Style::new().italic(true),
1823        };
1824
1825        let mut table = Table::new()
1826            .headers(vec!["Name", "Age", "City"])
1827            .row(vec!["Alice", "30", "New York"])
1828            .row(vec!["Bob", "25", "London"])
1829            .style_func(style_func);
1830
1831        let output = table.render();
1832        assert!(!output.is_empty());
1833        assert!(output.contains("Name")); // Headers should be present
1834        assert!(output.contains("Alice")); // Data should be present
1835
1836        // Since we're applying styles, there should be ANSI escape sequences
1837        assert!(output.contains("\\x1b[") || output.len() > 50); // Either ANSI codes or substantial content
1838    }
1839
1840    #[test]
1841    fn test_text_wrapping_functionality() {
1842        let mut table = Table::new()
1843            .headers(vec!["Short", "VeryLongContentThatShouldWrap"])
1844            .row(vec!["A", "This is a very long piece of content that should wrap across multiple lines when the table width is constrained"])
1845            .width(30)
1846            .wrap(true);
1847
1848        let output = table.render();
1849        assert!(!output.is_empty());
1850
1851        // With wrapping enabled and constrained width, we should get multiple lines
1852        let line_count = output.lines().count();
1853        assert!(
1854            line_count > 3,
1855            "Expected more than 3 lines due to wrapping, got {}",
1856            line_count
1857        );
1858    }
1859
1860    #[test]
1861    fn test_text_truncation_functionality() {
1862        let mut table = Table::new()
1863            .headers(vec!["Short", "Long"])
1864            .row(vec![
1865                "A",
1866                "This is a very long piece of content that should be truncated",
1867            ])
1868            .width(25)
1869            .wrap(false); // Disable wrapping to force truncation
1870
1871        let output = table.render();
1872        assert!(!output.is_empty());
1873
1874        // Should contain ellipsis indicating truncation
1875        assert!(
1876            output.contains("…"),
1877            "Expected ellipsis for truncated content"
1878        );
1879    }
1880
1881    #[test]
1882    fn test_ansi_aware_width_calculation() {
1883        use lipgloss::{Color, Style};
1884
1885        // Create content with ANSI sequences
1886        let styled_content = Style::new()
1887            .foreground(Color::from("#FF0000"))
1888            .bold(true)
1889            .render("Test");
1890
1891        let mut table = Table::new()
1892            .headers(vec!["Styled"])
1893            .row(vec![&styled_content])
1894            .width(10);
1895
1896        let output = table.render();
1897        assert!(!output.is_empty());
1898
1899        // The table should handle ANSI sequences correctly in width calculations
1900        // The visual width should be respected, not the character count
1901        for line in output.lines() {
1902            let visual_width = lipgloss::width(line);
1903            assert!(
1904                visual_width <= 10,
1905                "Line has visual width {} > 10: '{}'",
1906                visual_width,
1907                line
1908            );
1909        }
1910    }
1911
1912    #[test]
1913    fn test_predefined_style_functions() {
1914        // Test header_row_style
1915        let mut table1 = Table::new()
1916            .headers(vec!["Name", "Age"])
1917            .row(vec!["Alice", "30"])
1918            .style_func(header_row_style);
1919
1920        let output1 = table1.render();
1921        assert!(!output1.is_empty());
1922        assert!(output1.contains("Name"));
1923
1924        // Test zebra_style
1925        let mut table2 = Table::new()
1926            .headers(vec!["Item", "Count"])
1927            .row(vec!["Apple", "5"])
1928            .row(vec!["Banana", "3"])
1929            .row(vec!["Cherry", "8"])
1930            .style_func(zebra_style);
1931
1932        let output2 = table2.render();
1933        assert!(!output2.is_empty());
1934        assert!(output2.contains("Item"));
1935
1936        // Test minimal_style
1937        let mut table3 = Table::new()
1938            .headers(vec!["Name"])
1939            .row(vec!["Test"])
1940            .style_func(minimal_style);
1941
1942        let output3 = table3.render();
1943        assert!(!output3.is_empty());
1944        assert!(output3.contains("Name"));
1945    }
1946
1947    #[test]
1948    fn test_boxed_style_function() {
1949        use lipgloss::{
1950            color::{STATUS_ERROR, STATUS_WARNING},
1951            Style,
1952        };
1953
1954        // Create a closure that captures variables
1955        let error_color = STATUS_ERROR;
1956        let warning_color = STATUS_WARNING;
1957
1958        let mut table = Table::new()
1959            .headers(vec!["Status", "Message"])
1960            .row(vec!["ERROR", "Something went wrong"])
1961            .row(vec!["WARNING", "This is a warning"])
1962            .row(vec!["INFO", "Everything is fine"])
1963            .style_func_boxed(move |row: i32, col: usize| {
1964                if row == HEADER_ROW {
1965                    Style::new().bold(true)
1966                } else if col == 0 {
1967                    // Style the status column based on content
1968                    // Note: In a real implementation, you'd have access to the cell content
1969                    match row {
1970                        0 => Style::new().foreground(error_color.clone()),
1971                        1 => Style::new().foreground(warning_color.clone()),
1972                        _ => Style::new(),
1973                    }
1974                } else {
1975                    Style::new()
1976                }
1977            });
1978
1979        let output = table.render();
1980        assert!(!output.is_empty());
1981        assert!(output.contains("Status"));
1982        assert!(output.contains("ERROR"));
1983    }
1984}