lingora-tui 0.4.14

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, KeyCode, KeyEvent};
use lingora_core::prelude::QualifiedIdentifier;
use rat_event::{HandleEvent, Outcome, Regular};
use rat_focus::{FocusFlag, HasFocus};
use ratatui::{prelude::*, widgets::*};

use crate::theme::LingoraTheme;

#[derive(Debug, Default)]
pub struct IdentifierListState {
    focus_flag: FocusFlag,
    list_state: ListState,
    selected: Option<QualifiedIdentifier>,
    area: Rect,
}

impl IdentifierListState {
    pub fn selected(&self) -> Option<&QualifiedIdentifier> {
        self.selected.as_ref()
    }

    fn update_selected(&mut self, items: &[QualifiedIdentifier]) {
        self.selected = self
            .list_state
            .selected()
            .and_then(|i| items.get(i).cloned());
    }

    fn handle_key_event(&mut self, event: &KeyEvent) -> Outcome {
        match &event.code {
            KeyCode::Up => {
                self.list_state.select_previous();
                Outcome::Unchanged
            }
            KeyCode::Down => {
                self.list_state.select_next();
                Outcome::Unchanged
            }
            _ => Outcome::Continue,
        }
    }
}

impl HasFocus for IdentifierListState {
    fn build(&self, builder: &mut rat_focus::FocusBuilder) {
        builder.leaf_widget(self);
    }

    fn focus(&self) -> FocusFlag {
        self.focus_flag.clone()
    }

    fn area(&self) -> Rect {
        self.area
    }
}

impl HandleEvent<Event, Regular, Outcome> for IdentifierListState {
    fn handle(&mut self, event: &Event, _qualifier: Regular) -> Outcome {
        if self.focus_flag.is_focused() {
            match event {
                Event::Key(event) => self.handle_key_event(event),
                _ => Outcome::Continue,
            }
        } else {
            Outcome::Continue
        }
    }
}

pub struct IdentifierList<'a> {
    theme: &'a LingoraTheme,
    filtered_identifiers: Vec<QualifiedIdentifier>,
}

impl<'a> IdentifierList<'a> {
    pub fn new(
        theme: &'a LingoraTheme,
        filtered_identifiers: impl Iterator<Item = QualifiedIdentifier>,
    ) -> Self {
        let mut filtered_identifiers = Vec::from_iter(filtered_identifiers);
        filtered_identifiers.sort();

        Self {
            theme,
            filtered_identifiers,
        }
    }
}

impl StatefulWidget for IdentifierList<'_> {
    type State = IdentifierListState;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State)
    where
        Self: Sized,
    {
        state.area = area;

        state.update_selected(&self.filtered_identifiers);

        let list = List::new(
            self.filtered_identifiers
                .iter()
                .map(|s| Text::from(s.to_meta_string())),
        )
        .block(self.theme.focus_block(&state.focus_flag))
        .highlight_style(self.theme.selection())
        .highlight_symbol("ยป ")
        .highlight_spacing(HighlightSpacing::Always);

        StatefulWidget::render(list, area, buf, &mut state.list_state);
    }
}