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,
    text::Line,
    widgets::{Clear, Paragraph, Widget, Wrap},
};

use crate::{
    handler::message::Message,
    misc::{config::theme, osc52::CopyToClipboardOsc52},
    tui::{
        component::Component,
        tag_line::{Tag, TagLine},
        utils::Scroll,
        widgets::block::Block,
    },
};

#[derive(Debug)]
pub struct SheetSection {
    header: String,
    content: String,
}

impl SheetSection {
    pub fn new(header: String, content: String) -> Self {
        Self { header, content }
    }
}

#[derive(Debug)]
pub struct Sheet {
    scroll: Scroll,
    row: usize,
    sections: Vec<SheetSection>,
}

impl Sheet {
    pub fn new(row: usize, sections: Vec<SheetSection>) -> Self {
        Self {
            scroll: Default::default(),
            row,
            sections,
        }
    }

    pub fn scroll_up(&mut self) {
        self.scroll.up();
    }

    pub fn scroll_down(&mut self) {
        self.scroll.down();
    }

    pub fn row(&self) -> usize {
        self.row
    }

    pub fn set(&mut self, row: usize, sections: Vec<SheetSection>) {
        self.row = row;
        self.sections = sections;
    }
}

impl Component for Sheet {
    fn render(
        &mut self,
        area: ratatui::prelude::Rect,
        buf: &mut ratatui::prelude::Buffer,
        _focus_state: super::component::FocusState,
    ) {
        Clear.render(area, buf);

        let pg = Paragraph::new(
            self.sections
                .iter()
                .enumerate()
                .flat_map(|(idx, SheetSection { header, content })| {
                    std::iter::once(Line::raw(header).style(theme().header(idx)))
                        .chain(
                            content
                                .lines()
                                .map(|line| Line::raw(line).style(theme().text())),
                        )
                        .chain(std::iter::once(Line::raw("\n")))
                })
                .collect::<Vec<_>>(),
        )
        .style(theme().text())
        .alignment(Alignment::Left)
        .wrap(Wrap { trim: true })
        .block(
            Block::default()
                .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} "))
                        .tag(Tag::new(" Copy ", " C ")),
                )
                .title_alignment(Alignment::Center)
                .into_widget(),
        );

        self.scroll
            .adjust(pg.line_count(area.width), area.height.saturating_sub(2));

        pg.scroll((self.scroll.val_u16(), 0)).render(area, buf);
    }

    fn handle(&mut self, event: crossterm::event::KeyEvent) -> bool {
        match (event.code, event.modifiers) {
            (KeyCode::Char('K'), KeyModifiers::SHIFT) | (KeyCode::Up, KeyModifiers::SHIFT) => {
                self.scroll.up();
                true
            }
            (KeyCode::Char('J'), KeyModifiers::SHIFT) | (KeyCode::Down, KeyModifiers::SHIFT) => {
                self.scroll.down();
                true
            }
            (KeyCode::Char('c'), KeyModifiers::NONE) => {
                let text = self
                    .sections
                    .iter()
                    .map(|s| format!("{}\n{}", s.header, s.content))
                    .collect::<Vec<_>>()
                    .join("\n\n");
                text.copy_to_clipboard_via_osc52();
                Message::AppShowToast(format!("Row #{} copied to clipboard", self.row + 1))
                    .enqueue();
                true
            }
            (KeyCode::Esc, KeyModifiers::NONE) | (KeyCode::Char('q'), KeyModifiers::NONE) => {
                Message::PaneDismissModal.enqueue();
                true
            }

            _ => false,
        }
    }
}