Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5c83ab5af | ||
|
|
c8050f3e69 | ||
|
|
c69f4b0e38 | ||
|
|
be82f8638a | ||
|
|
914538241f | ||
|
|
dbdce75996 | ||
|
|
311f2fe7b4 | ||
|
|
7a8a1cb340 | ||
|
|
2526179d0b | ||
|
|
844fc57f1a |
No files matched your search
@@ -1,3 +1,13 @@
|
|||||||
# wordsmith
|
# wordsmith
|
||||||
|
|
||||||
Language learning utility.
|
Language learning utility.
|
||||||
|
|
||||||
|
## TODO:
|
||||||
|
|
||||||
|
- [ ] Docs
|
||||||
|
- [ ] On screen cursor
|
||||||
|
- [ ] Remove unwraps
|
||||||
|
- [ ] Default config
|
||||||
|
- [ ] No db fallbacks
|
||||||
|
- [ ] Better logs
|
||||||
|
- [ ] Translate / define window (https://libretranslate.com/?source=auto&target=en&q=Testt)
|
||||||
+9
-1
@@ -30,7 +30,15 @@ fn main() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = read_config(DEFAULT_CONFIG_PATH, default_config); // TODO: read config from param
|
let config_path: &str = {
|
||||||
|
if args.len() > 2 && args[1] == "--c" {
|
||||||
|
&args[2]
|
||||||
|
} else {
|
||||||
|
DEFAULT_CONFIG_PATH
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = read_config(config_path, default_config);
|
||||||
|
|
||||||
let word_source_result = FileDatabase::new(&config.database_path);
|
let word_source_result = FileDatabase::new(&config.database_path);
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ pub trait WordSource {
|
|||||||
fn find_by_word_and_lang(&self, word: &str, lang: &str) -> Vec<Word>;
|
fn find_by_word_and_lang(&self, word: &str, lang: &str) -> Vec<Word>;
|
||||||
fn insert_word(&self, word: Word) -> Result<(), ()>;
|
fn insert_word(&self, word: Word) -> Result<(), ()>;
|
||||||
fn remove_word(&self, word_key: &str) -> Result<(), ()>;
|
fn remove_word(&self, word_key: &str) -> Result<(), ()>;
|
||||||
fn edit_word(&self, word: Word) -> Result<Word, ()>;
|
fn edit_word(&self, word: Word, key: &str) -> Result<Word, ()>;
|
||||||
fn normalize_word_key(word_key: &str) -> String;
|
fn normalize_word_key(word_key: &str) -> String;
|
||||||
fn normalize_lang_key(word_key: &str) -> String;
|
fn normalize_lang_key(word_key: &str) -> String;
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ impl WordSource for FileDatabase {
|
|||||||
fn get_all_words(&self) -> Vec<Word> {
|
fn get_all_words(&self) -> Vec<Word> {
|
||||||
let statement = self
|
let statement = self
|
||||||
.connection
|
.connection
|
||||||
.prepare("select key, description, lang from word");
|
.prepare("select key, description, lang from word ORDER BY key ASC");
|
||||||
let mut word_list: Vec<Word> = vec![];
|
let mut word_list: Vec<Word> = vec![];
|
||||||
|
|
||||||
// TODO: make this pretty with functional programming
|
// TODO: make this pretty with functional programming
|
||||||
@@ -94,7 +94,7 @@ impl WordSource for FileDatabase {
|
|||||||
fn find_by_word(&self, word: &str) -> Vec<Word> {
|
fn find_by_word(&self, word: &str) -> Vec<Word> {
|
||||||
let statement = self
|
let statement = self
|
||||||
.connection
|
.connection
|
||||||
.prepare("SELECT key, description, lang FROM word");
|
.prepare("SELECT key, description, lang FROM word ORDER BY key ASC");
|
||||||
let mut word_list: Vec<Word> = vec![];
|
let mut word_list: Vec<Word> = vec![];
|
||||||
|
|
||||||
if let Ok(mut query) = statement {
|
if let Ok(mut query) = statement {
|
||||||
@@ -118,7 +118,7 @@ impl WordSource for FileDatabase {
|
|||||||
fn find_by_lang(&self, lang: &str) -> Vec<Word> {
|
fn find_by_lang(&self, lang: &str) -> Vec<Word> {
|
||||||
let statement = self
|
let statement = self
|
||||||
.connection
|
.connection
|
||||||
.prepare("select key, description, lang from word where lang = ?1");
|
.prepare("select key, description, lang from word where lang = ?1 ORDER BY key ASC");
|
||||||
let mut word_list: Vec<Word> = vec![];
|
let mut word_list: Vec<Word> = vec![];
|
||||||
|
|
||||||
// TODO: make this pretty with functional programming
|
// TODO: make this pretty with functional programming
|
||||||
@@ -142,7 +142,7 @@ impl WordSource for FileDatabase {
|
|||||||
fn find_by_word_and_lang(&self, word: &str, lang: &str) -> Vec<Word> {
|
fn find_by_word_and_lang(&self, word: &str, lang: &str) -> Vec<Word> {
|
||||||
let statement = self
|
let statement = self
|
||||||
.connection
|
.connection
|
||||||
.prepare("SELECT key, description, lang FROM word where lang = ?1");
|
.prepare("SELECT key, description, lang FROM word where lang = ?1 ORDER BY key ASC");
|
||||||
let mut word_list: Vec<Word> = vec![];
|
let mut word_list: Vec<Word> = vec![];
|
||||||
|
|
||||||
if let Ok(mut query) = statement {
|
if let Ok(mut query) = statement {
|
||||||
@@ -180,13 +180,14 @@ impl WordSource for FileDatabase {
|
|||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edit_word(&self, word: Word) -> Result<Word, ()> {
|
fn edit_word(&self, word: Word, key: &str) -> Result<Word, ()> {
|
||||||
let result = self.connection.execute(
|
let result = self.connection.execute(
|
||||||
"UPDATE word SET key = ?1, description = ?2, lang = ?3 WHERE key = ?1",
|
"UPDATE word SET key = ?1, description = ?2, lang = ?3 WHERE key = ?4",
|
||||||
(
|
(
|
||||||
&Self::normalize_word_key(&word.key),
|
&Self::normalize_word_key(&word.key),
|
||||||
&word.description,
|
&word.description,
|
||||||
&Self::normalize_lang_key(&word.lang),
|
&Self::normalize_lang_key(&word.lang),
|
||||||
|
key,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
match result {
|
match result {
|
||||||
|
|||||||
+146
-30
@@ -1,5 +1,7 @@
|
|||||||
use crate::modules::source::{FileDatabase, Word, WordSource};
|
use crate::modules::source::{FileDatabase, Word, WordSource};
|
||||||
|
use crate::modules::ui::app::UiState::FILTERLANG;
|
||||||
use crate::modules::ui::handler::handle_user_input;
|
use crate::modules::ui::handler::handle_user_input;
|
||||||
|
use crossterm::event::KeyEvent;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
DefaultTerminal, Frame,
|
DefaultTerminal, Frame,
|
||||||
layout::{Constraint, Direction, Flex, Layout},
|
layout::{Constraint, Direction, Flex, Layout},
|
||||||
@@ -41,6 +43,7 @@ pub struct App {
|
|||||||
pub word_to_edit: Option<Word>,
|
pub word_to_edit: Option<Word>,
|
||||||
pub word_filter: Option<String>,
|
pub word_filter: Option<String>,
|
||||||
pub lang_filter: Option<String>,
|
pub lang_filter: Option<String>,
|
||||||
|
pub last_key: Option<KeyEvent>,
|
||||||
pub exit: bool,
|
pub exit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +62,7 @@ impl App {
|
|||||||
word_to_edit: None,
|
word_to_edit: None,
|
||||||
word_filter: None,
|
word_filter: None,
|
||||||
lang_filter: None,
|
lang_filter: None,
|
||||||
|
last_key: None,
|
||||||
exit: false,
|
exit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +79,12 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, frame: &mut Frame) {
|
fn draw(&mut self, frame: &mut Frame) {
|
||||||
|
// PERF: this should not be declared on every frame
|
||||||
|
let color_bg_main = Color::default();
|
||||||
|
let color_fg_main = Color::default();
|
||||||
|
let color_fg_highlight = Color::Yellow;
|
||||||
|
let color_fg_delete = Color::LightRed;
|
||||||
|
|
||||||
let main_layout = Layout::default()
|
let main_layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.spacing(0)
|
.spacing(0)
|
||||||
@@ -82,7 +92,7 @@ impl App {
|
|||||||
.constraints(vec![
|
.constraints(vec![
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
Constraint::Fill(1),
|
Constraint::Fill(1),
|
||||||
Constraint::Length(3),
|
Constraint::Length(1),
|
||||||
])
|
])
|
||||||
.split(frame.area());
|
.split(frame.area());
|
||||||
|
|
||||||
@@ -103,8 +113,12 @@ impl App {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(word_filter.clone())
|
Paragraph::new(word_filter.clone()).block(
|
||||||
.block(Block::new().title("Word").borders(Borders::ALL)),
|
Block::new()
|
||||||
|
.title(self.format_ui_block_title("Word", UiState::FILTERWORD))
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
),
|
||||||
filters_layout[0],
|
filters_layout[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -116,41 +130,99 @@ impl App {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(lang_filter).block(Block::new().title("Lang").borders(Borders::ALL)),
|
Paragraph::new(lang_filter).block(
|
||||||
|
Block::new()
|
||||||
|
.title(self.format_ui_block_title("Lang", UiState::FILTERLANG))
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
),
|
||||||
filters_layout[1],
|
filters_layout[1],
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Word list / add / edit block
|
* Word list / add / edit block
|
||||||
* */
|
* */
|
||||||
if self.state == UiState::ADD {
|
let scroll_position: usize = {
|
||||||
|
if let Some(i) = self.list_state.selected() {
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// PERF: do not clone
|
||||||
|
let selected_word = self.word_list.clone().into_iter().nth(scroll_position);
|
||||||
|
if self.state == UiState::ADD || self.state == UiState::EDIT {
|
||||||
let add_words_heading_layout =
|
let add_words_heading_layout =
|
||||||
Layout::horizontal(vec![Constraint::Fill(1), Constraint::Length(8)])
|
Layout::horizontal(vec![Constraint::Fill(1), Constraint::Length(8)])
|
||||||
.spacing(1)
|
.spacing(1)
|
||||||
.split(main_layout[0]);
|
.split(main_layout[0]);
|
||||||
|
|
||||||
// PERF: maybe not clone
|
// PERF: maybe not clone
|
||||||
let word_to_add = self.word_to_add.clone().unwrap_or(Word {
|
let word_to_add = {
|
||||||
|
if self.state == UiState::EDIT
|
||||||
|
&& let Some(w) = self.word_to_edit.clone()
|
||||||
|
{
|
||||||
|
w
|
||||||
|
} else {
|
||||||
|
self.word_to_add.clone().unwrap_or(Word {
|
||||||
key: "".to_string(),
|
key: "".to_string(),
|
||||||
description: "".to_string(),
|
description: "".to_string(),
|
||||||
lang: "".to_string(),
|
lang: "".to_string(),
|
||||||
});
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(word_to_add.key)
|
Paragraph::new(word_to_add.key).block(
|
||||||
.block(Block::new().title("Word").borders(Borders::ALL)),
|
Block::new()
|
||||||
|
.title(self.format_add_block_title("Word", AddState::KEY))
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
),
|
||||||
add_words_heading_layout[0],
|
add_words_heading_layout[0],
|
||||||
);
|
);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(word_to_add.lang)
|
Paragraph::new(word_to_add.lang).block(
|
||||||
.block(Block::new().title("Lang").borders(Borders::ALL)),
|
Block::new()
|
||||||
|
.title(self.format_add_block_title("Lang", AddState::LANG))
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
),
|
||||||
add_words_heading_layout[1],
|
add_words_heading_layout[1],
|
||||||
);
|
);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(word_to_add.description)
|
Paragraph::new(word_to_add.description)
|
||||||
.block(Block::new().title("Description").borders(Borders::ALL)),
|
.wrap(Wrap { trim: true })
|
||||||
|
.block(
|
||||||
|
Block::new()
|
||||||
|
.title(
|
||||||
|
self.format_add_block_title("Description", AddState::DESCRIPTION),
|
||||||
|
)
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main)),
|
||||||
main_layout[1],
|
main_layout[1],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
let highligh_symbol: &str = {
|
||||||
|
if self.state == UiState::LIST {
|
||||||
|
"> "
|
||||||
|
} else if self.state == UiState::DELETE {
|
||||||
|
"x "
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let highligh_color: Color = {
|
||||||
|
if self.state == UiState::LIST {
|
||||||
|
Color::Yellow
|
||||||
|
} else if self.state == UiState::DELETE {
|
||||||
|
Color::LightRed
|
||||||
|
} else {
|
||||||
|
Color::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let words_layout =
|
let words_layout =
|
||||||
Layout::horizontal(vec![Constraint::Percentage(30), Constraint::Percentage(70)])
|
Layout::horizontal(vec![Constraint::Percentage(30), Constraint::Percentage(70)])
|
||||||
.split(main_layout[1]);
|
.split(main_layout[1]);
|
||||||
@@ -165,30 +237,23 @@ impl App {
|
|||||||
.direction(ListDirection::TopToBottom)
|
.direction(ListDirection::TopToBottom)
|
||||||
.block(
|
.block(
|
||||||
Block::default()
|
Block::default()
|
||||||
.title("Entries")
|
.title(self.format_ui_block_title("Entries", UiState::LIST))
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main))
|
||||||
.padding(Padding::horizontal(1)),
|
.padding(Padding::horizontal(1)),
|
||||||
)
|
)
|
||||||
.highlight_style(Style::default().fg(Color::default()))
|
.highlight_style(Style::default().fg(highligh_color))
|
||||||
.highlight_symbol("> ")
|
.highlight_symbol(highligh_symbol)
|
||||||
.style(Style::default().fg(Color::DarkGray));
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main));
|
||||||
frame.render_stateful_widget(word_key_list, words_layout[0], &mut self.list_state);
|
frame.render_stateful_widget(word_key_list, words_layout[0], &mut self.list_state);
|
||||||
|
|
||||||
let scroll_position: usize = {
|
if let Some(w) = selected_word.clone() {
|
||||||
if let Some(i) = self.list_state.selected() {
|
|
||||||
i
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// PERF: do not clone
|
|
||||||
let selected_word = self.word_list.clone().into_iter().nth(scroll_position);
|
|
||||||
if let Some(w) = selected_word {
|
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(w.description)
|
Paragraph::new(w.description)
|
||||||
.wrap(Wrap { trim: true })
|
.wrap(Wrap { trim: true })
|
||||||
.scroll((self.description_scroll, 0))
|
.scroll((self.description_scroll, 0))
|
||||||
.block(Block::new().borders(Borders::ALL)),
|
.block(Block::new().borders(Borders::ALL))
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main)),
|
||||||
words_layout[1],
|
words_layout[1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -201,14 +266,35 @@ impl App {
|
|||||||
.flex(Flex::Center)
|
.flex(Flex::Center)
|
||||||
.split(main_layout[2]);
|
.split(main_layout[2]);
|
||||||
|
|
||||||
let help_text = Text::raw("(f)ilter | (l)ang | (d)elete | (e)dit | (a)dd | (q)uit ")
|
let help_text = {
|
||||||
.style(Style::new().fg(Color::DarkGray));
|
if self.state == UiState::DELETE
|
||||||
|
&& let Some(s_w) = selected_word.clone()
|
||||||
|
{
|
||||||
|
Text::raw(format!("Sure you want to delete '{0}'? (y)es", s_w.key))
|
||||||
|
.style(Style::default().fg(color_fg_delete).bg(color_bg_main))
|
||||||
|
} else if self.state == UiState::ADD {
|
||||||
|
Text::raw("Adding item (enter) to confirm (esc) to exit")
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main))
|
||||||
|
} else {
|
||||||
|
Text::raw("(f)ilter | (l)ang | (d)elete | (e)dit | (a)dd | (q)uit")
|
||||||
|
.style(Style::default().fg(color_fg_main).bg(color_bg_main))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let help_area = help_layout[0].centered(
|
let help_area = help_layout[0].centered(
|
||||||
Constraint::Length(help_text.width() as u16),
|
Constraint::Length(help_text.width() as u16),
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
frame.render_widget(help_text, help_area);
|
frame.render_widget(help_text, help_area);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* State block
|
||||||
|
* */
|
||||||
|
// let state_layout = Layout::horizontal(vec![Constraint::Fill(1)])
|
||||||
|
// .flex(Flex::Center)
|
||||||
|
// .split(main_layout[2]);
|
||||||
|
// frame.render_widget(help_text, help_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_word_list(&mut self) {
|
pub fn update_word_list(&mut self) {
|
||||||
@@ -226,11 +312,41 @@ impl App {
|
|||||||
self.file_database.get_all_words()
|
self.file_database.get_all_words()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.list_state.select(Some(0));
|
|
||||||
self.description_scroll = 0;
|
self.description_scroll = 0;
|
||||||
self.word_list = word_list;
|
self.word_list = word_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn format_ui_block_title(&self, input: &str, matching_state: UiState) -> String {
|
||||||
|
if matching_state == self.state {
|
||||||
|
let mut result = input.to_string();
|
||||||
|
result.push_str(" ● ");
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
input.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_add_block_title(&self, input: &str, matching_state: AddState) -> String {
|
||||||
|
if matching_state == self.add_state {
|
||||||
|
let mut result = input.to_string();
|
||||||
|
result.push_str(" ● ");
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
input.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_selected_word(&self) -> Option<Word> {
|
||||||
|
let scroll_position: usize = {
|
||||||
|
if let Some(i) = self.list_state.selected() {
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.word_list.clone().into_iter().nth(scroll_position)
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self) -> io::Result<()> {
|
fn handle_events(&mut self) -> io::Result<()> {
|
||||||
return handle_user_input(self);
|
return handle_user_input(self);
|
||||||
}
|
}
|
||||||
|
|||||||
+158
-2
@@ -6,12 +6,36 @@ use crate::modules::{
|
|||||||
source::{Word, WordSource},
|
source::{Word, WordSource},
|
||||||
ui::app::{AddState, App, UiState},
|
ui::app::{AddState, App, UiState},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn handle_user_input(app: &mut App) -> io::Result<()> {
|
pub fn handle_user_input(app: &mut App) -> io::Result<()> {
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
event::KeyCode::Char(c) => {
|
event::KeyCode::Char(c) => {
|
||||||
match app.state {
|
match app.state {
|
||||||
|
UiState::DELETE => {
|
||||||
|
if c == 'y' || c == 'Y' || c == 's' || c == 'S' {
|
||||||
|
// delete word
|
||||||
|
let scroll_position: usize = {
|
||||||
|
if let Some(i) = app.list_state.selected() {
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let selected_word =
|
||||||
|
app.word_list.clone().into_iter().nth(scroll_position);
|
||||||
|
if let Some(w) = selected_word {
|
||||||
|
// TODO: manage result with some notification context
|
||||||
|
let _ = app.file_database.remove_word(&w.key);
|
||||||
|
app.update_word_list();
|
||||||
|
if scroll_position > 0 {
|
||||||
|
app.list_state.select(Some(scroll_position - 1));
|
||||||
|
}
|
||||||
|
app.state = UiState::LIST;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
app.state = UiState::LIST;
|
||||||
|
}
|
||||||
|
}
|
||||||
UiState::FILTERWORD => {
|
UiState::FILTERWORD => {
|
||||||
if let Some(mut prev_word) = app.word_filter.clone() {
|
if let Some(mut prev_word) = app.word_filter.clone() {
|
||||||
prev_word.push(c);
|
prev_word.push(c);
|
||||||
@@ -68,10 +92,48 @@ pub fn handle_user_input(app: &mut App) -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
UiState::EDIT => match app.add_state {
|
||||||
|
AddState::KEY => {
|
||||||
|
if let Some(mut prev_word) = app.word_to_edit.clone() {
|
||||||
|
prev_word.key.push(c);
|
||||||
|
app.word_to_edit = Some(prev_word);
|
||||||
|
} else {
|
||||||
|
app.word_to_edit = Some(Word {
|
||||||
|
key: c.to_string(),
|
||||||
|
description: "".to_string(),
|
||||||
|
lang: "".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddState::DESCRIPTION => {
|
||||||
|
if let Some(mut prev_word) = app.word_to_edit.clone() {
|
||||||
|
prev_word.description.push(c);
|
||||||
|
app.word_to_edit = Some(prev_word);
|
||||||
|
} else {
|
||||||
|
app.word_to_edit = Some(Word {
|
||||||
|
key: c.to_string(),
|
||||||
|
description: "".to_string(),
|
||||||
|
lang: "".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddState::LANG => {
|
||||||
|
if let Some(mut prev_word) = app.word_to_edit.clone() {
|
||||||
|
prev_word.lang.push(c);
|
||||||
|
app.word_to_edit = Some(prev_word);
|
||||||
|
} else {
|
||||||
|
app.word_to_edit = Some(Word {
|
||||||
|
key: c.to_string(),
|
||||||
|
description: "".to_string(),
|
||||||
|
lang: "".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
// Quit
|
// Quit
|
||||||
if c == 'q' || c == 'x' {
|
if c == 'q' || c == 'x' {
|
||||||
return Err(io::Error::last_os_error()); // FIXME: this is not right
|
app.exit = true;
|
||||||
}
|
}
|
||||||
// State change
|
// State change
|
||||||
if c == 'f' || c == '/' {
|
if c == 'f' || c == '/' {
|
||||||
@@ -85,15 +147,41 @@ pub fn handle_user_input(app: &mut App) -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
if c == 'e' {
|
if c == 'e' {
|
||||||
app.state = UiState::EDIT;
|
app.state = UiState::EDIT;
|
||||||
|
let selected_word = app.get_selected_word();
|
||||||
|
app.word_to_edit = selected_word;
|
||||||
}
|
}
|
||||||
if c == 'a' {
|
if c == 'a' {
|
||||||
app.state = UiState::ADD;
|
app.state = UiState::ADD;
|
||||||
}
|
}
|
||||||
// Movement
|
// Movement
|
||||||
if c == 'j' {
|
if c == 'j' {
|
||||||
|
let scroll_position: usize = {
|
||||||
|
if let Some(i) = app.list_state.selected() {
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if scroll_position < app.word_list.len() - 1 {
|
||||||
app.list_state.select_next();
|
app.list_state.select_next();
|
||||||
app.description_scroll = 0;
|
app.description_scroll = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if c == 'g' {
|
||||||
|
if let Some(g) = app.last_key {
|
||||||
|
match g.code {
|
||||||
|
event::KeyCode::Char(g_c) => {
|
||||||
|
if g_c == 'g' {
|
||||||
|
app.list_state.select_first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c == 'G' {
|
||||||
|
app.list_state.select_last();
|
||||||
|
}
|
||||||
if c == 'k' {
|
if c == 'k' {
|
||||||
app.list_state.select_previous();
|
app.list_state.select_previous();
|
||||||
app.description_scroll = 0;
|
app.description_scroll = 0;
|
||||||
@@ -114,6 +202,19 @@ pub fn handle_user_input(app: &mut App) -> io::Result<()> {
|
|||||||
if let Some(w) = app.word_to_add.clone()
|
if let Some(w) = app.word_to_add.clone()
|
||||||
&& w.key.len() > 0
|
&& w.key.len() > 0
|
||||||
{
|
{
|
||||||
|
if app.add_state == AddState::DESCRIPTION {
|
||||||
|
let new_line_char = '\n';
|
||||||
|
if let Some(mut prev_word) = app.word_to_add.clone() {
|
||||||
|
prev_word.description.push(new_line_char);
|
||||||
|
app.word_to_add = Some(prev_word);
|
||||||
|
} else {
|
||||||
|
app.word_to_add = Some(Word {
|
||||||
|
key: new_line_char.to_string(),
|
||||||
|
description: "".to_string(),
|
||||||
|
lang: "".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
let res = app.file_database.insert_word(w);
|
let res = app.file_database.insert_word(w);
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -125,12 +226,46 @@ pub fn handle_user_input(app: &mut App) -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
UiState::EDIT => {
|
||||||
|
if let Some(w) = app.word_to_edit.clone()
|
||||||
|
&& let Some(w_k) = app.get_selected_word()
|
||||||
|
{
|
||||||
|
if app.add_state == AddState::DESCRIPTION {
|
||||||
|
let new_line_char = '\n';
|
||||||
|
if let Some(mut prev_word) = app.word_to_edit.clone() {
|
||||||
|
let new_line_char = '\n';
|
||||||
|
prev_word.description.push(new_line_char);
|
||||||
|
app.word_to_edit = Some(prev_word);
|
||||||
|
} else {
|
||||||
|
app.word_to_edit = Some(Word {
|
||||||
|
key: new_line_char.to_string(),
|
||||||
|
description: "".to_string(),
|
||||||
|
lang: "".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let res = app.file_database.edit_word(w.clone(), &w_k.key);
|
||||||
|
match res {
|
||||||
|
Ok(_) => {
|
||||||
|
app.update_word_list();
|
||||||
|
app.word_to_edit = None;
|
||||||
|
app.state = get_next_ui_state(app.state);
|
||||||
|
}
|
||||||
|
Err(_) => app.state = get_next_ui_state(app.state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
event::KeyCode::Tab => match app.state {
|
event::KeyCode::Tab => match app.state {
|
||||||
UiState::ADD => {
|
UiState::ADD => {
|
||||||
app.add_state = get_next_add_state(app.add_state);
|
app.add_state = get_next_add_state(app.add_state);
|
||||||
}
|
}
|
||||||
|
UiState::EDIT => {
|
||||||
|
app.add_state = get_next_add_state(app.add_state);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
app.state = get_next_ui_state(app.state);
|
app.state = get_next_ui_state(app.state);
|
||||||
}
|
}
|
||||||
@@ -164,6 +299,26 @@ pub fn handle_user_input(app: &mut App) -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
UiState::EDIT => match app.add_state {
|
||||||
|
AddState::KEY => {
|
||||||
|
if let Some(mut prev_word) = app.word_to_edit.clone() {
|
||||||
|
prev_word.key.pop();
|
||||||
|
app.word_to_edit = Some(prev_word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddState::DESCRIPTION => {
|
||||||
|
if let Some(mut prev_word) = app.word_to_edit.clone() {
|
||||||
|
prev_word.description.pop();
|
||||||
|
app.word_to_edit = Some(prev_word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddState::LANG => {
|
||||||
|
if let Some(mut prev_word) = app.word_to_edit.clone() {
|
||||||
|
prev_word.lang.pop();
|
||||||
|
app.word_to_edit = Some(prev_word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
event::KeyCode::Esc => {
|
event::KeyCode::Esc => {
|
||||||
@@ -171,6 +326,7 @@ pub fn handle_user_input(app: &mut App) -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
_ => return Ok(()),
|
_ => return Ok(()),
|
||||||
}
|
}
|
||||||
|
app.last_key = Some(key);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user