term_kit/
table.rs

1use crossterm::{
2    cursor, execute,
3    style::{Color, Print, PrintStyledContent, Stylize},
4    terminal::{Clear, ClearType},
5};
6use std::io::{stdout, Write};
7
8pub struct Table {
9    title: String,
10    rows: i16,
11    columns: i16,
12    data: Vec<Vec<String>>,
13    border_color: Color,
14    title_color: Color,
15    data_color: Color,
16}
17
18impl Table {
19    pub fn new(
20        title: String, 
21        rows: i16, 
22        border_color: Option<Color>, 
23        title_color: Option<Color>,
24        data: Option<Vec<Vec<String>>>,
25    ) -> Self {
26        let data = data.unwrap_or_else(|| vec![vec!["".to_string(); 2 as usize]; rows as usize]);
27        
28        Table {
29            title,
30            rows,
31            columns: 2,
32            border_color: border_color.unwrap_or(Color::Blue),
33            title_color: title_color.unwrap_or(Color::White),
34            data,
35            data_color: Color::White, // default data color
36        }
37    }
38
39    /// Override the default column count
40    /// This is not recommended, but can be used with cautious amounts of columns because of terminal width restrictions.
41    pub fn with_column_count_override(mut self, columns: i16) -> Self {
42        self.columns = columns;
43        for row in &mut self.data {
44            row.resize(columns as usize, "".to_string());
45        }
46        self
47    }
48
49    /// Set value for a specific cell
50    pub fn set_cell(&mut self, row: i16, column: i16, value: String) {
51        if row >= 0 && row < self.rows && column >= 0 && column < self.columns {
52            self.data[row as usize][column as usize] = value;
53        }
54    }
55
56    /// Calculate the maximum width of each column based on the longest string in that column
57    fn calculate_column_widths(&self) -> Vec<usize> {
58        let mut column_widths = vec![0; self.columns as usize];
59
60        for col in 0..self.columns {
61            let mut max_len = 0;
62            for row in 0..self.rows {
63                let cell_value = &self.data[row as usize][col as usize];
64                max_len = max_len.max(cell_value.len());
65            }
66            column_widths[col as usize] = max_len;
67        }
68
69        column_widths
70    }
71
72    pub fn render(&self) -> Result<(), Box<dyn std::error::Error>> {
73        let mut stdout = stdout();
74        let column_widths = self.calculate_column_widths(); // Get dynamic column widths
75        let total_width: usize = column_widths.iter().sum::<usize>() + (self.columns as usize - 1); // Space between columns
76
77        execute!(stdout, cursor::MoveToColumn(0))?;
78        execute!(stdout, Clear(ClearType::UntilNewLine))?;
79        execute!(
80            stdout,
81            PrintStyledContent(format!("{: <width$}", self.title, width = total_width).with(self.title_color))
82        )?;
83        execute!(stdout, Print("\n"))?;
84
85        // Print top border
86        execute!(stdout, PrintStyledContent("┌".with(self.border_color)))?;
87        for (i, width) in column_widths.iter().enumerate() {
88            execute!(stdout, PrintStyledContent(format!("{:─<width$}", "", width = width).with(self.border_color)))?;
89            if i < self.columns as usize - 1 {
90                execute!(stdout, PrintStyledContent("┬".with(self.border_color)))?;
91            }
92        }
93        execute!(stdout, PrintStyledContent("┐\n".with(self.border_color)))?;
94
95        // Print table rows
96        for row in 0..self.rows {
97            execute!(stdout, PrintStyledContent("│".with(self.border_color)))?;
98            for (col, width) in column_widths.iter().enumerate() {
99                let cell_value = &self.data[row as usize][col];
100                let padded_value = format!("{:<width$}", cell_value, width = width);
101                execute!(stdout, PrintStyledContent(padded_value.with(self.data_color)))?;
102                execute!(stdout, PrintStyledContent("│".with(self.border_color)))?;
103            }
104            execute!(stdout, Print("\n"))?;
105
106            if row < self.rows - 1 {
107                execute!(stdout, PrintStyledContent("├".with(self.border_color)))?;
108                for (i, width) in column_widths.iter().enumerate() {
109                    execute!(stdout, PrintStyledContent(format!("{:─<width$}", "", width = width).with(self.border_color)))?;
110                    if i < self.columns as usize - 1 {
111                        execute!(stdout, PrintStyledContent("┼".with(self.border_color)))?;
112                    }
113                }
114                execute!(stdout, PrintStyledContent("┤\n".with(self.border_color)))?;
115            }
116        }
117
118        // Print bottom border
119        execute!(stdout, PrintStyledContent("└".with(self.border_color)))?;
120        for (i, width) in column_widths.iter().enumerate() {
121            execute!(stdout, PrintStyledContent(format!("{:─<width$}", "", width = width).with(self.border_color)))?;
122            if i < self.columns as usize - 1 {
123                execute!(stdout, PrintStyledContent("┴".with(self.border_color)))?;
124            }
125        }
126        execute!(stdout, PrintStyledContent("┘\n".with(self.border_color)))?;
127
128        stdout.flush()?;
129
130        Ok(())
131    }
132}