tabiew 0.15.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, Rect},
    text::{Line, Span},
    widgets::{Block, List, ListItem, ListState, StatefulWidget},
};

use crate::{
    handler::message::Message,
    misc::{buffer_ext::BufferExt, config::theme},
    tui::{
        app_default::{AppDefault, AppTitle},
        component::Component,
        icons,
        tag_line::{Tag, TagLine},
    },
};

#[derive(Debug)]
pub struct TabSwitcher {
    title: String,
    list: ListState,
    tabs: Vec<String>,
    rollback: usize,
}

impl TabSwitcher {
    pub fn new(title: impl Into<String>, tabs: Vec<String>, idx: usize) -> TabSwitcher {
        Self {
            title: title.into(),
            rollback: idx,
            list: ListState::default().with_selected(Some(idx)),
            tabs,
        }
    }

    pub fn selected(&self) -> Option<usize> {
        self.list.selected()
    }

    pub fn select(&mut self, idx: impl Into<Option<usize>>) {
        self.list.select(idx.into());
    }

    pub fn select_prev(&mut self) {
        self.list.select_previous();
    }

    pub fn select_next(&mut self) {
        let idx = self
            .list
            .selected()
            .unwrap_or_default()
            .saturating_add(1)
            .min(self.tabs.len().saturating_sub(1));
        self.list.select(Some(idx));
    }

    pub fn select_first(&mut self) {
        self.list.select(Some(0));
    }
    pub fn select_last(&mut self) {
        self.list.select(Some(self.tabs.len().saturating_sub(1)));
    }
}

impl Component for TabSwitcher {
    fn render(
        &mut self,
        area: Rect,
        buf: &mut ratatui::prelude::Buffer,
        _focus_state: super::component::FocusState,
    ) {
        buf.clear(area);
        let num_width = self.tabs.len().to_string().len();
        StatefulWidget::render(
            List::app_default()
                .items(self.tabs.iter().enumerate().map(|(idx, tab)| {
                    ListItem::from(Line::from(vec![
                        Span::raw(format!("{:>num_width$}. ", idx + 1)).style(theme().subtext()),
                        Span::raw(tab.as_str()).style(theme().text()),
                    ]))
                }))
                .block(
                    Block::app_default()
                        .app_title(self.title.as_str())
                        .title_bottom(
                            TagLine::new()
                                .mono_color()
                                .centered()
                                .tag(Tag::new(icons::TRASH.str("Close"), "Backspace")),
                        )
                        .title_alignment(Alignment::Center),
                ),
            area,
            buf,
            &mut ListState::default().with_selected(self.list.selected()),
        );
    }

    fn handle(&mut self, event: crossterm::event::KeyEvent) -> bool {
        match (event.code, event.modifiers) {
            (KeyCode::Up, KeyModifiers::NONE)
            | (KeyCode::Char('k'), KeyModifiers::NONE)
            | (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
                self.select_prev();
                Message::TabsSelect(self.selected().unwrap_or_default()).enqueue();
                true
            }
            (KeyCode::Down, KeyModifiers::NONE)
            | (KeyCode::Char('j'), KeyModifiers::NONE)
            | (KeyCode::Char('n'), KeyModifiers::CONTROL) => {
                self.select_next();
                Message::TabsSelect(self.selected().unwrap_or_default()).enqueue();
                true
            }
            (KeyCode::Home, KeyModifiers::NONE) | (KeyCode::Char('g'), KeyModifiers::NONE) => {
                self.select_first();
                Message::TabsSelect(self.selected().unwrap_or_default()).enqueue();
                true
            }
            (KeyCode::End, KeyModifiers::NONE) | (KeyCode::Char('G'), KeyModifiers::SHIFT) => {
                self.select_last();
                Message::TabsSelect(self.selected().unwrap_or_default()).enqueue();
                true
            }
            (KeyCode::Enter, KeyModifiers::NONE) => {
                Message::TabsDismissSwitcher.enqueue();
                true
            }
            (KeyCode::Backspace, KeyModifiers::NONE) => {
                Message::TabsCloseSelected.enqueue();
                true
            }
            (KeyCode::Esc, KeyModifiers::NONE)
            | (KeyCode::Char('q'), KeyModifiers::NONE)
            | (KeyCode::Char('t'), KeyModifiers::NONE) => {
                Message::TabsDismissSwitcher.enqueue();
                Message::TabsSelect(self.rollback).enqueue();
                true
            }
            _ => false,
        }
    }
}