tabiew 0.13.1

A lightweight TUI application to view and query tabular data files, such as CSV, TSV, and parquet.
use crossterm::event::{KeyCode, KeyModifiers};
use ratatui::{
    layout::{Alignment, Constraint},
    text::Text,
    widgets::{Borders, Clear, Row, StatefulWidget, Table, TableState, Widget},
};

use crate::{
    misc::{config::theme, sql::TableSchema, type_ext::human_readable_size},
    tui::{
        component::Component,
        tag_line::{Tag, TagLine},
        widgets::block::Block,
    },
};

#[derive(Debug)]
pub struct DataFrameFieldInfo {
    table_state: TableState,
    table_schema: TableSchema,
}

impl DataFrameFieldInfo {
    pub fn new(table_schema: TableSchema) -> Self {
        Self {
            table_state: Default::default(),
            table_schema,
        }
    }

    pub fn table_state(&self) -> &TableState {
        &self.table_state
    }

    pub fn table_state_mut(&mut self) -> &mut TableState {
        &mut self.table_state
    }

    pub fn scroll_up(&mut self) {
        *self.table_state.offset_mut() = self.table_state.offset().saturating_sub(1)
    }

    pub fn scroll_down(&mut self) {
        *self.table_state.offset_mut() = self.table_state.offset().saturating_add(1)
    }
}

impl Component for DataFrameFieldInfo {
    fn render(
        &mut self,
        area: ratatui::prelude::Rect,
        buf: &mut ratatui::prelude::Buffer,
        _focus_state: crate::tui::component::FocusState,
    ) {
        *self.table_state.offset_mut() = self.table_state.offset().min(
            self.table_schema
                .len()
                .saturating_sub(area.height.saturating_sub(2).into()),
        );
        Widget::render(Clear, area, buf);
        StatefulWidget::render(
            Table::default()
                .header(
                    Row::new(
                        ["Name", "Type", "Estimated Size", "Null Count", "Min", "Max"]
                            .into_iter()
                            .enumerate()
                            .map(|(i, s)| Text::styled(s, theme().header(i))),
                    )
                    .style(theme().table_header()),
                )
                .rows(
                    self.table_schema
                        .iter()
                        .enumerate()
                        .map(|(idx, (name, info))| {
                            Row::new([
                                name.to_owned(),
                                format!("{}", info.dtype()),
                                human_readable_size(info.estimated_size() as u64),
                                format!("{}", info.null_count()),
                                info.min().to_string(),
                                info.max().to_string(),
                            ])
                            .style(theme().row(idx))
                        }),
                )
                .widths([
                    Constraint::Fill(1),
                    Constraint::Fill(1),
                    Constraint::Fill(1),
                    Constraint::Fill(1),
                    Constraint::Fill(1),
                    Constraint::Fill(1),
                ])
                .style(theme().text())
                .block(
                    Block::default()
                        .borders(Borders::BOTTOM | Borders::RIGHT | Borders::LEFT)
                        .bottom(
                            TagLine::new()
                                .mono_color()
                                .centered()
                                .tag(Tag::new(" Scroll Up ", " Shift+K | Shift+\u{2191} "))
                                .tag(Tag::new(" Scroll Down ", " Shift+J | Shift+\u{2193} ")),
                        )
                        .title_alignment(Alignment::Center)
                        .into_widget(),
                ),
            area,
            buf,
            &mut self.table_state,
        );
    }

    fn handle(&mut self, event: crossterm::event::KeyEvent) -> bool {
        match event.code {
            KeyCode::Up if event.modifiers == KeyModifiers::SHIFT => {
                self.scroll_up();
                true
            }
            KeyCode::Down if event.modifiers == KeyModifiers::SHIFT => {
                self.scroll_down();
                true
            }
            KeyCode::Char('K') => {
                self.scroll_up();
                true
            }
            KeyCode::Char('J') => {
                self.scroll_down();
                true
            }
            _ => false,
        }
    }
}