feat: word list cache + word filtering

This commit is contained in:
2026-08-02 20:14:20 +02:00
parent 3e5d3fa555
commit 09d5deebb1

View File

@@ -1,16 +1,16 @@
use crate::modules::source::{FileDatabase, WordSource}; use crate::modules::source::{FileDatabase, Word, WordSource};
use crossterm::event::{ use crossterm::event::{
self, self,
Event::{self, Key}, Event::{self},
}; };
use ratatui::{ use ratatui::{
DefaultTerminal, Frame, DefaultTerminal, Frame,
layout::{Constraint, Direction, Flex, Layout}, layout::{Constraint, Direction, Flex, Layout},
style::{Color, Style}, style::{Color, Style},
text::Text, text::Text,
widgets::{Block, Borders, Padding, Paragraph}, widgets::{Block, Borders, Paragraph},
}; };
use std::{io, ops::Index}; use std::io;
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum UiState { pub enum UiState {
@@ -26,6 +26,7 @@ pub struct App {
state: UiState, state: UiState,
file_database: FileDatabase, file_database: FileDatabase,
scroll_position: usize, scroll_position: usize,
word_list: Vec<Word>,
word_filter: Option<String>, word_filter: Option<String>,
lang_filter: Option<String>, lang_filter: Option<String>,
exit: bool, exit: bool,
@@ -33,10 +34,13 @@ pub struct App {
impl App { impl App {
pub fn new(db: FileDatabase) -> Self { pub fn new(db: FileDatabase) -> Self {
let wl = db.get_all_words();
Self { Self {
state: UiState::LIST, state: UiState::LIST,
file_database: db, file_database: db,
scroll_position: 0, scroll_position: 0,
word_list: wl,
word_filter: None, word_filter: None,
lang_filter: None, lang_filter: None,
exit: false, exit: false,
@@ -105,25 +109,13 @@ impl App {
filters_layout[1], filters_layout[1],
); );
// FIXME: this is wrong, the list must reside in memory and only be updated when required,
// this makes a new call every frame, insane
let word_list = {
if let Some(w_filter) = &self.word_filter
&& let Some(l_filter) = &self.lang_filter
{
self.file_database
.find_by_word_and_lang(&word_filter, &l_filter)
} else if let Some(l_filter) = &self.lang_filter {
self.file_database.find_by_lang(&l_filter)
} else {
self.file_database.get_all_words()
}
};
frame.render_widget( frame.render_widget(
Paragraph::new("Word list").block(Block::new().borders(Borders::ALL).title("Entries")), Paragraph::new("Word list").block(Block::new().borders(Borders::ALL).title("Entries")),
words_layout[0], words_layout[0],
); );
let selected_word = word_list.into_iter().nth(self.scroll_position);
// PERF: do not clone
let selected_word = self.word_list.clone().into_iter().nth(self.scroll_position);
if let Some(w) = selected_word { if let Some(w) = selected_word {
frame.render_widget( frame.render_widget(
Paragraph::new(w.description).block(Block::new().borders(Borders::ALL)), Paragraph::new(w.description).block(Block::new().borders(Borders::ALL)),
@@ -141,6 +133,25 @@ impl App {
frame.render_widget(help_text, help_area); frame.render_widget(help_text, help_area);
} }
fn update_word_list(&mut self) {
let word_list = {
if let Some(w_filter) = &self.word_filter
&& let Some(l_filter) = &self.lang_filter
{
self.file_database
.find_by_word_and_lang(&w_filter, &l_filter)
} else if let Some(w_filter) = &self.word_filter {
self.file_database.find_by_word(&w_filter)
} else if let Some(l_filter) = &self.lang_filter {
self.file_database.find_by_lang(&l_filter)
} else {
self.file_database.get_all_words()
}
};
self.scroll_position = 0;
self.word_list = word_list;
}
fn handle_events(&mut self) -> io::Result<()> { fn handle_events(&mut self) -> io::Result<()> {
if let Event::Key(key) = event::read()? { if let Event::Key(key) = event::read()? {
match key.code { match key.code {
@@ -153,6 +164,7 @@ impl App {
} else { } else {
self.word_filter = Some(c.to_string()); self.word_filter = Some(c.to_string());
} }
self.update_word_list();
} }
UiState::FILTERLANG => { UiState::FILTERLANG => {
if let Some(mut prev_lang) = self.lang_filter.clone() { if let Some(mut prev_lang) = self.lang_filter.clone() {
@@ -161,6 +173,7 @@ impl App {
} else { } else {
self.lang_filter = Some(c.to_string()); self.lang_filter = Some(c.to_string());
} }
self.update_word_list();
} }
_ => { _ => {
// Quit // Quit
@@ -185,14 +198,34 @@ impl App {
} }
// Movement // Movement
if c == 'j' { if c == 'j' {
self.scroll_position -= 1; if self.scroll_position > 0 {
self.scroll_position -= 1;
}
} }
if c == 'k' { if c == 'k' {
self.scroll_position += 1; if self.scroll_position < self.word_list.len() - 1 {
self.scroll_position += 1;
}
} }
} }
} }
} }
event::KeyCode::Backspace => match self.state {
UiState::FILTERWORD => {
if let Some(w) = &self.word_filter {
if w.len() <= 1 {
self.word_filter = None;
} else {
let mut new_w = w.clone();
new_w.pop();
self.word_filter = Some(new_w);
}
self.update_word_list();
}
}
UiState::FILTERLANG => {}
_ => {}
},
event::KeyCode::Esc => { event::KeyCode::Esc => {
self.state = UiState::LIST; self.state = UiState::LIST;
} }