aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock13
-rw-r--r--Cargo.toml1
-rw-r--r--src/backend.rs1
-rw-r--r--src/backend/search.rs40
-rw-r--r--src/frontend/app.rs38
-rw-r--r--src/frontend/handler.rs12
-rw-r--r--src/frontend/ui.rs113
7 files changed, 167 insertions, 51 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f7c299e..bdc0996 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -99,6 +99,7 @@ dependencies = [
"crossterm",
"futures",
"itertools",
+ "nucleo-matcher",
"ratatui",
"regex",
"sarge",
@@ -606,7 +607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.6",
]
[[package]]
@@ -717,6 +718,16 @@ dependencies = [
]
[[package]]
+name = "nucleo-matcher"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85"
+dependencies = [
+ "memchr",
+ "unicode-segmentation",
+]
+
+[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 7983fbc..cc62a61 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ biblatex = "0.9.3"
crossterm = { version = "0.28.1", features = ["event-stream"] }
futures = "0.3.30"
itertools = "0.13.0"
+nucleo-matcher = "0.3.1"
# ratatui = "0.28.1"
ratatui = { version = "0.28.1", features = ["unstable-rendered-line-info"]}
regex = "1.10.6"
diff --git a/src/backend.rs b/src/backend.rs
index 586496b..75adb9f 100644
--- a/src/backend.rs
+++ b/src/backend.rs
@@ -17,3 +17,4 @@
pub mod bib;
pub mod cliargs;
+pub mod search;
diff --git a/src/backend/search.rs b/src/backend/search.rs
new file mode 100644
index 0000000..ae874db
--- /dev/null
+++ b/src/backend/search.rs
@@ -0,0 +1,40 @@
+use nucleo_matcher::{
+ pattern::{CaseMatching, Normalization, Pattern},
+ Config, Matcher,
+};
+use std::collections::HashMap;
+
+// Stringify inner Vec<String> by joining/concat
+fn convert_to_string(inner_vec: &Vec<String>) -> String {
+ inner_vec.join(" ")
+}
+
+// Return a filtered entry list
+pub fn search_entry_list(search_pattern: &str, orig_list: Vec<Vec<String>>) -> Vec<Vec<String>> {
+ // Create a hashmap to connect stingified entry with entry vec
+ let mut entry_string_hm: HashMap<String, Vec<String>> = HashMap::new();
+
+ // Convert all entries to string and insert them into the hashmap
+ // next to the original inner Vec<String> of the entry list
+ for entry in orig_list {
+ entry_string_hm.insert(convert_to_string(&entry), entry);
+ }
+
+ // Set up matcher (TODO: One time needed only, move to higher level)
+ let mut matcher = Matcher::new(Config::DEFAULT);
+
+ // Filter the stringified entries and collect them into a vec
+ let filtered_matches: Vec<String> = {
+ let matches = Pattern::parse(search_pattern, CaseMatching::Ignore, Normalization::Smart)
+ .match_list(entry_string_hm.keys(), &mut matcher);
+ matches.into_iter().map(|f| f.0.to_string()).collect()
+ };
+
+ // Create filtered entry list and push the inner entry vec's to it
+ // Use the filtered stringified hm-key as index
+ let mut filtered_list: Vec<Vec<String>> = Vec::new();
+ for m in filtered_matches {
+ filtered_list.push(entry_string_hm[&m].to_owned());
+ }
+ filtered_list
+}
diff --git a/src/frontend/app.rs b/src/frontend/app.rs
index 75a6ede..970a064 100644
--- a/src/frontend/app.rs
+++ b/src/frontend/app.rs
@@ -15,7 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-use crate::backend::bib::*;
+use crate::backend::{bib::*, search};
use std::error;
use arboard::Clipboard;
@@ -39,6 +39,7 @@ pub enum CurrentArea {
pub enum FormerArea {
EntryArea,
TagArea,
+ SearchArea,
}
// Application.
@@ -211,13 +212,20 @@ impl App {
// Toggle moveable list between entries and tags
pub fn toggle_area(&mut self) {
match self.current_area {
- CurrentArea::EntryArea => self.current_area = CurrentArea::TagArea,
- CurrentArea::TagArea => self.current_area = CurrentArea::EntryArea,
+ CurrentArea::EntryArea => {
+ self.current_area = CurrentArea::TagArea;
+ self.tag_list.tag_list_state.select(Some(0))
+ }
+ CurrentArea::TagArea => {
+ self.current_area = CurrentArea::EntryArea;
+ self.tag_list.tag_list_state.select(None)
+ }
CurrentArea::SearchArea => {
if let Some(former_area) = &self.former_area {
match former_area {
FormerArea::EntryArea => self.current_area = CurrentArea::EntryArea,
FormerArea::TagArea => self.current_area = CurrentArea::TagArea,
+ _ => {}
}
}
}
@@ -226,6 +234,7 @@ impl App {
match former_area {
FormerArea::EntryArea => self.current_area = CurrentArea::EntryArea,
FormerArea::TagArea => self.current_area = CurrentArea::TagArea,
+ FormerArea::SearchArea => self.current_area = CurrentArea::SearchArea,
}
}
}
@@ -273,9 +282,11 @@ impl App {
}
pub fn select_first(&mut self) {
- self.scroll_info = 0;
match self.current_area {
- CurrentArea::EntryArea => self.entry_table.entry_table_state.select_first(),
+ CurrentArea::EntryArea => {
+ self.scroll_info = 0;
+ self.entry_table.entry_table_state.select_first();
+ }
CurrentArea::TagArea => self.tag_list.tag_list_state.select_first(),
_ => {}
}
@@ -283,9 +294,11 @@ impl App {
}
pub fn select_last(&mut self) {
- self.scroll_info = 0;
match self.current_area {
- CurrentArea::EntryArea => self.entry_table.entry_table_state.select_last(),
+ CurrentArea::EntryArea => {
+ self.scroll_info = 0;
+ self.entry_table.entry_table_state.select_last();
+ }
CurrentArea::TagArea => self.tag_list.tag_list_state.select_last(),
_ => {}
}
@@ -305,4 +318,15 @@ impl App {
let yanked_text = selection.to_string();
clipboard.set_text(yanked_text).unwrap();
}
+
+ // Search entry list
+ pub fn search_entries(&mut self) {
+ let orig_list = &self.biblio_data.entry_list.bibentries;
+ let filtered_list = search::search_entry_list(&self.search_string, orig_list.clone());
+ self.entry_table = EntryTable::from_iter(filtered_list)
+ }
+
+ pub fn reset_entry_table(&mut self) {
+ self.entry_table = EntryTable::from_iter(self.biblio_data.entry_list.bibentries.clone())
+ }
}
diff --git a/src/frontend/handler.rs b/src/frontend/handler.rs
index 27aa8de..573795c 100644
--- a/src/frontend/handler.rs
+++ b/src/frontend/handler.rs
@@ -18,7 +18,7 @@
use crate::frontend::app::{App, AppResult};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
-use super::app::{CurrentArea, FormerArea};
+use super::app::{CurrentArea, EntryTable, FormerArea};
/// Handles the key events and updates the state of [`App`].
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
@@ -97,6 +97,11 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
KeyCode::Tab | KeyCode::BackTab => {
app.toggle_area();
}
+ KeyCode::Esc => {
+ if let Some(FormerArea::SearchArea) = app.former_area {
+ app.reset_entry_table();
+ }
+ }
_ => {}
},
// Keycodes for the search area (popup)
@@ -105,18 +110,21 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
app.toggle_area();
app.former_area = None;
app.search_string.clear();
+ app.reset_entry_table();
}
KeyCode::Enter => {
// TODO: run function for filtering the list
app.toggle_area();
- app.former_area = None;
+ app.former_area = Some(FormerArea::SearchArea);
app.search_string.clear();
}
KeyCode::Backspace => {
app.search_string.pop();
+ app.search_entries();
}
KeyCode::Char(search_pattern) => {
app.search_string.push(search_pattern);
+ app.search_entries();
}
_ => {}
},
diff --git a/src/frontend/ui.rs b/src/frontend/ui.rs
index 0e44e5c..5ca7100 100644
--- a/src/frontend/ui.rs
+++ b/src/frontend/ui.rs
@@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
+use futures::SinkExt;
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
@@ -35,7 +36,7 @@ use crate::{
frontend::app::{App, TagListItem},
};
-use super::app::CurrentArea;
+use super::app::{CurrentArea, FormerArea};
const MAIN_BLUE_COLOR: Color = Color::Indexed(39);
const MAIN_PURPLE_COLOR: Color = Color::Indexed(129);
@@ -47,6 +48,7 @@ const SELECTED_STYLE: Style = Style::new()
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::REVERSED);
const TEXT_FG_COLOR: Color = SLATE.c200;
+const TEXT_CONFIRMED: Style = Style::new().fg(Color::Green);
pub const fn alternate_colors(i: usize) -> Color {
if i % 2 == 0 {
@@ -109,8 +111,25 @@ impl App {
pub fn render_footer(&mut self, area: Rect, buf: &mut Buffer) {
match &self.current_area {
CurrentArea::SearchArea => {
+ let search_title = {
+ match self.former_area {
+ Some(FormerArea::EntryArea) => {
+ let search_title = " Search Entries ".to_string();
+ search_title
+ }
+ Some(FormerArea::TagArea) => {
+ let search_title = " Search Keywords ".to_string();
+ search_title
+ }
+ _ => {
+ let search_title = " Search ".to_string();
+ search_title
+ }
+ }
+ };
+
let block = Block::bordered()
- .title(" Search Entries ")
+ .title(search_title)
.border_set(symbols::border::ROUNDED);
Paragraph::new(self.search_string.clone())
.block(block)
@@ -196,45 +215,57 @@ impl App {
// We get the info depending on the item's state.
// TODO: Implement logic showin informations for selected entry:
let style_value = Style::new().bold();
- let mut lines = vec![];
- lines.push(Line::from(vec![
- Span::styled("Authors: ", style_value),
- Span::styled(
- String::from(BibiEntry::get_authors(
- &self.get_selected_citekey(),
- &self.main_biblio.bibliography,
- )),
- Style::new().green(),
- ),
- ]));
- lines.push(Line::from(vec![
- Span::styled("Title: ", style_value),
- Span::styled(
- String::from(BibiEntry::get_title(
- &self.get_selected_citekey(),
- &self.main_biblio.bibliography,
- )),
- Style::new().magenta(),
- ),
- ]));
- lines.push(Line::from(vec![
- Span::styled("Year: ", style_value),
- Span::styled(
- String::from(BibiEntry::get_year(
- &self.get_selected_citekey(),
- &self.main_biblio.bibliography,
- )),
- Style::new().light_magenta(),
- ),
- ]));
- lines.push(Line::from(""));
- lines.push(Line::from(vec![Span::styled(
- String::from(BibiEntry::get_abstract(
- &self.get_selected_citekey(),
- &self.main_biblio.bibliography,
- )),
- Style::default(),
- )]));
+ let lines = {
+ // if self.entry_table.entry_table_items.len() > 0 {
+ if self.entry_table.entry_table_state.selected().is_some() {
+ let mut lines = vec![];
+ lines.push(Line::from(vec![
+ Span::styled("Authors: ", style_value),
+ Span::styled(
+ String::from(BibiEntry::get_authors(
+ &self.get_selected_citekey(),
+ &self.main_biblio.bibliography,
+ )),
+ Style::new().green(),
+ ),
+ ]));
+ lines.push(Line::from(vec![
+ Span::styled("Title: ", style_value),
+ Span::styled(
+ String::from(BibiEntry::get_title(
+ &self.get_selected_citekey(),
+ &self.main_biblio.bibliography,
+ )),
+ Style::new().magenta(),
+ ),
+ ]));
+ lines.push(Line::from(vec![
+ Span::styled("Year: ", style_value),
+ Span::styled(
+ String::from(BibiEntry::get_year(
+ &self.get_selected_citekey(),
+ &self.main_biblio.bibliography,
+ )),
+ Style::new().light_magenta(),
+ ),
+ ]));
+ lines.push(Line::from(""));
+ lines.push(Line::from(vec![Span::styled(
+ String::from(BibiEntry::get_abstract(
+ &self.get_selected_citekey(),
+ &self.main_biblio.bibliography,
+ )),
+ Style::default(),
+ )]));
+ lines
+ } else {
+ let lines = vec![
+ Line::from(" "),
+ Line::from("No entry selected".bold().into_centered_line().red()),
+ ];
+ lines
+ }
+ };
let info = Text::from(lines);
// We show the list item's info under the list in this paragraph