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