lingora-tui 0.4.11

Lingora is a free and open-source localization management program that analyses fluent translation files highlighting discrepancies between reference and target languages. This application provides a terminal user interface; run as `lingora-tui`.
Documentation
use crossterm::event::Event;
use lingora_core::prelude::QualifiedIdentifier;
use rat_event::{ConsumedEvent, HandleEvent, Outcome, Regular};
use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
use rat_text::HasScreenCursor;
use ratatui::prelude::*;

use crate::{
    components::{
        Cursor, IdentifierFilter, IdentifierFilterState, IdentifierList, IdentifierListState,
    },
    theme::LingoraTheme,
};

#[derive(Debug, Default)]
pub struct IdentifiersState {
    filter_state: IdentifierFilterState,
    list_state: IdentifierListState,
}

impl IdentifiersState {
    #[inline(always)]
    pub fn filter(&self) -> &str {
        self.filter_state.text()
    }

    #[inline]
    pub fn selected(&self) -> Option<&QualifiedIdentifier> {
        self.list_state.selected()
    }
}

impl HasFocus for IdentifiersState {
    fn build(&self, builder: &mut FocusBuilder) {
        builder.widget(&self.filter_state);
        builder.widget(&self.list_state);
    }

    fn focus(&self) -> FocusFlag {
        unreachable!()
    }

    fn area(&self) -> Rect {
        unreachable!()
    }
}

impl HasScreenCursor for IdentifiersState {
    fn screen_cursor(&self) -> Cursor {
        self.filter_state.screen_cursor()
    }
}

impl HandleEvent<Event, Regular, Outcome> for IdentifiersState {
    fn handle(&mut self, event: &Event, qualifier: Regular) -> Outcome {
        self.filter_state
            .handle(event, qualifier)
            .or_else(|| self.list_state.handle(event, qualifier))
    }
}

pub struct Identifiers<'a> {
    theme: &'a LingoraTheme,
    identifiers: Vec<QualifiedIdentifier>,
}

impl<'a> Identifiers<'a> {
    pub fn new(
        theme: &'a LingoraTheme,
        identifiers: impl Iterator<Item = QualifiedIdentifier>,
    ) -> Self {
        let identifiers = Vec::from_iter(identifiers);
        Self { theme, identifiers }
    }
}

impl StatefulWidget for &Identifiers<'_> {
    type State = IdentifiersState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
    where
        Self: Sized,
    {
        let filter = state.filter_state.text().to_ascii_lowercase();
        let filtered_identifiers = self
            .identifiers
            .iter()
            .filter(|id| id.to_meta_string().to_ascii_lowercase().contains(&filter));

        let filter = IdentifierFilter::new(self.theme);
        let list = IdentifierList::new(self.theme, filtered_identifiers.cloned());

        let chunks = Layout::vertical(vec![Constraint::Length(3), Constraint::Fill(1)]).split(area);
        filter.render(chunks[0], buf, &mut state.filter_state);
        list.render(chunks[1], buf, &mut state.list_state);
    }
}