aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlukeflo2024-10-22 21:52:36 +0200
committerlukeflo2024-10-22 21:52:36 +0200
commit66402a9c23e0975a8a3d8c2707b689b9cde98ccf (patch)
treeccba415674b13eadb6739f5a4d0cb53642dc2e62
parent0a74206015e764551ec2a0ade8f6853e915b6911 (diff)
downloadbibiman-66402a9c23e0975a8a3d8c2707b689b9cde98ccf.tar.gz
bibiman-66402a9c23e0975a8a3d8c2707b689b9cde98ccf.zip
rearrange code, file and folder structure
-rw-r--r--Cargo.lock24
-rw-r--r--Cargo.toml2
-rw-r--r--src/bib.rs (renamed from src/backend.rs)5
-rw-r--r--src/bib/bibmain.rs (renamed from src/backend/bib.rs)0
-rw-r--r--src/bib/entries.rs258
-rw-r--r--src/bib/keywords.rs55
-rw-r--r--src/bib/search.rs (renamed from src/backend/search.rs)3
-rw-r--r--src/cliargs.rs (renamed from src/backend/cliargs.rs)0
-rw-r--r--src/frontend.rs23
-rw-r--r--src/frontend/keywords.rs159
-rw-r--r--src/main.rs9
-rw-r--r--src/tui.rs (renamed from src/frontend/tui.rs)17
-rw-r--r--src/tui/app.rs (renamed from src/frontend/app.rs)12
-rw-r--r--src/tui/command.rs (renamed from src/frontend/entries.rs)332
-rw-r--r--src/tui/handler.rs (renamed from src/frontend/handler.rs)4
-rw-r--r--src/tui/ui.rs (renamed from src/frontend/ui.rs)27
16 files changed, 466 insertions, 464 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4373b63..156a27c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -71,7 +71,7 @@ dependencies = [
[[package]]
name = "bibiman"
-version = "0.4.3"
+version = "0.4.4"
dependencies = [
"arboard",
"biblatex",
@@ -717,6 +717,12 @@ dependencies = [
]
[[package]]
+name = "indoc"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
+
+[[package]]
name = "instability"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1127,23 +1133,23 @@ dependencies = [
[[package]]
name = "ratatui"
-version = "0.28.1"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d"
+checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
dependencies = [
"bitflags 2.6.0",
"cassowary",
"compact_str",
"crossterm",
+ "indoc",
"instability",
"itertools",
"lru",
"paste",
"strum",
- "strum_macros",
"unicode-segmentation",
"unicode-truncate",
- "unicode-width",
+ "unicode-width 0.2.0",
]
[[package]]
@@ -1587,7 +1593,7 @@ checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools",
"unicode-segmentation",
- "unicode-width",
+ "unicode-width 0.1.14",
]
[[package]]
@@ -1597,6 +1603,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
+[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 01f6b91..1346a21 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,7 @@ futures = "0.3.30"
hayagriva = "0.8.0"
itertools = "0.13.0"
nucleo-matcher = "0.3.1"
-ratatui = { version = "0.28.1", features = ["unstable-rendered-line-info"]}
+ratatui = { version = "0.29.0", features = ["unstable-rendered-line-info"]}
sarge = "7.2.5"
signal-hook = "0.3.17"
tokio = { version = "1.39.3", features = ["full"] }
diff --git a/src/backend.rs b/src/bib.rs
index 75adb9f..8443b9a 100644
--- a/src/backend.rs
+++ b/src/bib.rs
@@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-pub mod bib;
-pub mod cliargs;
+pub mod bibmain;
+pub mod entries;
+pub mod keywords;
pub mod search;
diff --git a/src/backend/bib.rs b/src/bib/bibmain.rs
index a7df951..a7df951 100644
--- a/src/backend/bib.rs
+++ b/src/bib/bibmain.rs
diff --git a/src/bib/entries.rs b/src/bib/entries.rs
new file mode 100644
index 0000000..41edba8
--- /dev/null
+++ b/src/bib/entries.rs
@@ -0,0 +1,258 @@
+// bibiman - a TUI for managing BibLaTeX databases
+// Copyright (C) 2024 lukeflo
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+/////
+
+use crate::bib::bibmain::BibiData;
+use ratatui::widgets::{ScrollbarState, TableState};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum EntryTableColumn {
+ Authors,
+ Title,
+ Year,
+ Pubtype,
+}
+
+// Define list containing entries as table
+#[derive(Debug, PartialEq, Eq)]
+pub struct EntryTable {
+ pub entry_table_items: Vec<EntryTableItem>,
+ pub entry_table_at_search_start: Vec<EntryTableItem>,
+ pub entry_table_selected_column: EntryTableColumn,
+ pub entry_table_sorted_by_col: EntryTableColumn,
+ pub entry_table_reversed_sort: bool,
+ pub entry_table_state: TableState,
+ pub entry_scroll_state: ScrollbarState,
+ pub entry_info_scroll: u16,
+ pub entry_info_scroll_state: ScrollbarState,
+}
+
+impl EntryTable {
+ pub fn new(entry_list: Vec<BibiData>) -> Self {
+ let entry_table_items = Self::set_entry_table(entry_list);
+ let entry_table_state = TableState::default().with_selected(0);
+ let entry_scroll_state = ScrollbarState::new(entry_table_items.len());
+ let entry_info_scroll_state = ScrollbarState::default();
+ Self {
+ entry_table_items,
+ entry_table_at_search_start: Vec::new(),
+ entry_table_selected_column: EntryTableColumn::Authors,
+ entry_table_sorted_by_col: EntryTableColumn::Authors,
+ entry_table_reversed_sort: false,
+ entry_table_state,
+ entry_scroll_state,
+ entry_info_scroll: 0,
+ entry_info_scroll_state,
+ }
+ }
+
+ pub fn set_entry_table(entry_list: Vec<BibiData>) -> Vec<EntryTableItem> {
+ let mut entry_table: Vec<EntryTableItem> = entry_list
+ .into_iter()
+ .map(|e| EntryTableItem {
+ authors: e.authors,
+ short_author: String::new(),
+ title: e.title,
+ year: e.year,
+ pubtype: e.pubtype,
+ keywords: e.keywords,
+ citekey: e.citekey,
+ abstract_text: e.abstract_text,
+ doi_url: e.doi_url,
+ filepath: e.filepath,
+ })
+ .collect();
+
+ entry_table.sort_by(|a, b| a.authors.to_lowercase().cmp(&b.authors.to_lowercase()));
+ entry_table
+ }
+
+ // Sort entry table by specific column.
+ // Toggle sorting by hitting same key again
+ pub fn sort_entry_table(&mut self, toggle: bool) {
+ if toggle {
+ self.entry_table_reversed_sort = !self.entry_table_reversed_sort;
+ }
+ if self.entry_table_selected_column != self.entry_table_sorted_by_col {
+ self.entry_table_reversed_sort = false
+ }
+ self.entry_table_sorted_by_col = self.entry_table_selected_column.clone();
+ if self.entry_table_reversed_sort {
+ match self.entry_table_selected_column {
+ EntryTableColumn::Authors => self
+ .entry_table_items
+ .sort_by(|a, b| b.authors.to_lowercase().cmp(&a.authors.to_lowercase())),
+ EntryTableColumn::Title => self
+ .entry_table_items
+ .sort_by(|a, b| b.title.to_lowercase().cmp(&a.title.to_lowercase())),
+ EntryTableColumn::Year => self
+ .entry_table_items
+ .sort_by(|a, b| b.year.to_lowercase().cmp(&a.year.to_lowercase())),
+ EntryTableColumn::Pubtype => self
+ .entry_table_items
+ .sort_by(|a, b| b.pubtype.to_lowercase().cmp(&a.pubtype.to_lowercase())),
+ }
+ } else if !self.entry_table_reversed_sort {
+ match self.entry_table_selected_column {
+ EntryTableColumn::Authors => self
+ .entry_table_items
+ .sort_by(|a, b| a.authors.to_lowercase().cmp(&b.authors.to_lowercase())),
+ EntryTableColumn::Title => self
+ .entry_table_items
+ .sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase())),
+ EntryTableColumn::Year => self
+ .entry_table_items
+ .sort_by(|a, b| a.year.to_lowercase().cmp(&b.year.to_lowercase())),
+ EntryTableColumn::Pubtype => self
+ .entry_table_items
+ .sort_by(|a, b| a.pubtype.to_lowercase().cmp(&b.pubtype.to_lowercase())),
+ }
+ }
+ }
+}
+
+// Define contents of each entry table row
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct EntryTableItem {
+ pub authors: String,
+ pub short_author: String,
+ pub title: String,
+ pub year: String,
+ pub pubtype: String,
+ pub keywords: String,
+ pub citekey: String,
+ pub abstract_text: String,
+ pub doi_url: String,
+ pub filepath: String,
+}
+
+impl EntryTableItem {
+ // This functions decides which fields are rendered in the entry table
+ // Fields which should be usable but not visible can be left out
+ pub fn ref_vec(&mut self) -> Vec<&str> {
+ self.short_author = match self.authors.split_once(",") {
+ Some((first, _rest)) => {
+ if self.authors.contains("(ed.)") {
+ let first_author = format!("{} et al. (ed.)", first);
+ first_author
+ } else {
+ let first_author = format!("{} et al.", first);
+ first_author
+ }
+ }
+ None => String::from(""),
+ };
+
+ vec![
+ {
+ if self.short_author.is_empty() {
+ &self.authors
+ } else {
+ &self.short_author
+ }
+ },
+ &self.title,
+ &self.year,
+ &self.pubtype,
+ ]
+ }
+
+ pub fn authors(&self) -> &str {
+ &self.authors
+ }
+
+ pub fn title(&self) -> &str {
+ &self.title
+ }
+
+ pub fn year(&self) -> &str {
+ &self.year
+ }
+
+ pub fn pubtype(&self) -> &str {
+ &self.pubtype
+ }
+
+ pub fn citekey(&self) -> &str {
+ &self.citekey
+ }
+
+ pub fn doi_url(&self) -> &str {
+ &self.doi_url
+ }
+
+ pub fn filepath(&self) -> &str {
+ &self.filepath
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::EntryTableItem;
+
+ #[test]
+ fn check_os() {
+ let os = std::env::consts::OS;
+ assert_eq!(
+ os,
+ "linux",
+ "You're not coding on linux, but on {}... Switch to linux, now!",
+ std::env::consts::OS
+ )
+ }
+
+ #[test]
+ fn shorten_authors() {
+ let mut entry: EntryTableItem = EntryTableItem {
+ authors: "Miller, Schmitz, Bernard".to_string(),
+ short_author: "".to_string(),
+ title: "A title".to_string(),
+ year: "2000".to_string(),
+ pubtype: "article".to_string(),
+ keywords: "key1, key2".to_string(),
+ citekey: "miller_2000".to_string(),
+ abstract_text: "An abstract".to_string(),
+ doi_url: "www.text.org".to_string(),
+ filepath: "/home/test".to_string(),
+ };
+
+ let entry_vec = EntryTableItem::ref_vec(&mut entry);
+
+ let mut entry_editors: EntryTableItem = EntryTableItem {
+ authors: "Miller, Schmitz, Bernard (ed.)".to_string(),
+ short_author: "".to_string(),
+ title: "A title".to_string(),
+ year: "2000".to_string(),
+ pubtype: "article".to_string(),
+ keywords: "key1, key2".to_string(),
+ citekey: "miller_2000".to_string(),
+ abstract_text: "An abstract".to_string(),
+ doi_url: "www.text.org".to_string(),
+ filepath: "/home/test".to_string(),
+ };
+
+ let entry_vec_editors = EntryTableItem::ref_vec(&mut entry_editors);
+
+ assert_eq!(
+ entry_vec,
+ vec!["Miller et al.", "A title", "2000", "article"]
+ );
+ assert_eq!(
+ entry_vec_editors,
+ vec!["Miller et al. (ed.)", "A title", "2000", "article"]
+ )
+ }
+}
diff --git a/src/bib/keywords.rs b/src/bib/keywords.rs
new file mode 100644
index 0000000..2668323
--- /dev/null
+++ b/src/bib/keywords.rs
@@ -0,0 +1,55 @@
+// bibiman - a TUI for managing BibLaTeX databases
+// Copyright (C) 2024 lukeflo
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+/////
+
+use ratatui::widgets::{ListState, ScrollbarState};
+
+#[derive(Debug)]
+pub struct TagList {
+ pub tag_list_items: Vec<String>,
+ pub tag_list_state: ListState,
+ pub tag_scroll_state: ScrollbarState,
+ pub selected_keywords: Vec<String>,
+}
+
+// Structure of the list items.
+#[derive(Debug)]
+pub struct TagListItem {
+ pub keyword: String,
+}
+
+// Function to process inputed characters and convert them (to string, or more complex function)
+impl TagListItem {
+ pub fn new(info: &str) -> Self {
+ Self {
+ keyword: info.to_string(),
+ }
+ }
+}
+
+impl TagList {
+ pub fn new(keyword_list: Vec<String>) -> Self {
+ let tag_list_items = keyword_list;
+ let tag_list_state = ListState::default(); // for preselection: .with_selected(Some(0));
+ let tag_scroll_state = ScrollbarState::new(tag_list_items.len());
+ Self {
+ tag_list_items,
+ tag_list_state,
+ tag_scroll_state,
+ selected_keywords: Vec::new(),
+ }
+ }
+}
diff --git a/src/backend/search.rs b/src/bib/search.rs
index 65d97d7..f6e8d14 100644
--- a/src/backend/search.rs
+++ b/src/bib/search.rs
@@ -1,11 +1,10 @@
+use super::entries::EntryTableItem;
use nucleo_matcher::{
pattern::{CaseMatching, Normalization, Pattern},
Config, Matcher,
};
use std::collections::HashMap;
-use crate::frontend::entries::EntryTableItem;
-
#[derive(Debug)]
pub struct BibiSearch {
pub search_string: String, // Search string show in footer, used for search
diff --git a/src/backend/cliargs.rs b/src/cliargs.rs
index d3a4652..d3a4652 100644
--- a/src/backend/cliargs.rs
+++ b/src/cliargs.rs
diff --git a/src/frontend.rs b/src/frontend.rs
deleted file mode 100644
index dc16cb5..0000000
--- a/src/frontend.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-// bibiman - a TUI for managing BibLaTeX databases
-// Copyright (C) 2024 lukeflo
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-/////
-
-pub mod app;
-pub mod entries;
-pub mod handler;
-pub mod keywords;
-pub mod tui;
-pub mod ui;
diff --git a/src/frontend/keywords.rs b/src/frontend/keywords.rs
deleted file mode 100644
index 5605a59..0000000
--- a/src/frontend/keywords.rs
+++ /dev/null
@@ -1,159 +0,0 @@
-// bibiman - a TUI for managing BibLaTeX databases
-// Copyright (C) 2024 lukeflo
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-/////
-
-use super::app::{App, FormerArea};
-use crate::backend::search::BibiSearch;
-use ratatui::widgets::{ListState, ScrollbarState};
-
-#[derive(Debug)]
-pub struct TagList {
- pub tag_list_items: Vec<String>,
- pub tag_list_state: ListState,
- pub tag_scroll_state: ScrollbarState,
- pub selected_keywords: Vec<String>,
-}
-
-// Structure of the list items.
-#[derive(Debug)]
-pub struct TagListItem {
- pub keyword: String,
-}
-
-// Function to process inputed characters and convert them (to string, or more complex function)
-impl TagListItem {
- pub fn new(info: &str) -> Self {
- Self {
- keyword: info.to_string(),
- }
- }
-}
-
-impl TagList {
- pub fn new(keyword_list: Vec<String>) -> Self {
- let tag_list_items = keyword_list;
- let tag_list_state = ListState::default(); // for preselection: .with_selected(Some(0));
- let tag_scroll_state = ScrollbarState::new(tag_list_items.len());
- Self {
- tag_list_items,
- tag_list_state,
- tag_scroll_state,
- selected_keywords: Vec::new(),
- }
- }
-}
-
-impl App {
- // Tag List commands
-
- // Movement
- pub fn select_next_tag(&mut self, keywords: u16) {
- self.tag_list.tag_list_state.scroll_down_by(keywords);
- self.tag_list.tag_scroll_state = self
- .tag_list
- .tag_scroll_state
- .position(self.tag_list.tag_list_state.selected().unwrap());
- }
-
- pub fn select_previous_tag(&mut self, keywords: u16) {
- self.tag_list.tag_list_state.scroll_up_by(keywords);
- self.tag_list.tag_scroll_state = self
- .tag_list
- .tag_scroll_state
- .position(self.tag_list.tag_list_state.selected().unwrap());
- }
-
- pub fn select_first_tag(&mut self) {
- self.tag_list.tag_list_state.select_first();
- self.tag_list.tag_scroll_state = self.tag_list.tag_scroll_state.position(0);
- }
-
- pub fn select_last_tag(&mut self) {
- self.tag_list.tag_list_state.select_last();
- self.tag_list.tag_scroll_state = self
- .tag_list
- .tag_scroll_state
- .position(self.tag_list.tag_list_items.len());
- }
-
- pub fn get_selected_tag(&self) -> &str {
- let idx = self.tag_list.tag_list_state.selected().unwrap();
- let keyword = &self.tag_list.tag_list_items[idx];
- // let keyword = &self.tag_list.tag_list_items[idx].keyword;
- keyword
- }
-
- pub fn search_tags(&mut self) {
- let orig_list = &self.main_biblio.keyword_list;
- let filtered_list =
- BibiSearch::search_tag_list(&self.search_struct.search_string, orig_list.clone());
- self.tag_list.tag_list_items = filtered_list;
- // Update scrollbar length after filtering list
- self.tag_list.tag_scroll_state = ScrollbarState::content_length(
- self.tag_list.tag_scroll_state,
- self.tag_list.tag_list_items.len(),
- );
- }
-
- pub fn filter_tags_by_entries(&mut self) {
- let mut filtered_keywords: Vec<String> = Vec::new();
-
- let orig_list = &self.entry_table.entry_table_items;
-
- for e in orig_list {
- if !e.keywords.is_empty() {
- let mut key_vec: Vec<String> = e
- .keywords
- .split(',')
- .map(|s| s.trim().to_string())
- .filter(|s| !s.is_empty())
- .collect();
- filtered_keywords.append(&mut key_vec);
- }
- }
-
- filtered_keywords.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
- filtered_keywords.dedup();
-
- self.search_struct.filtered_tag_list = filtered_keywords.clone();
- self.tag_list.tag_list_items = filtered_keywords;
- self.tag_list.tag_scroll_state = ScrollbarState::content_length(
- self.tag_list.tag_scroll_state,
- self.tag_list.tag_list_items.len(),
- );
- }
-
- // Filter the entry list by tags when hitting enter
- // If already inside a filtered tag or entry list, apply the filtering
- // to the already filtered list only
- pub fn filter_for_tags(&mut self) {
- let orig_list = &self.entry_table.entry_table_items;
- let keyword = self.get_selected_tag();
- let filtered_list = BibiSearch::filter_entries_by_tag(&keyword, &orig_list);
- // self.tag_list.selected_keyword = keyword.to_string();
- self.tag_list.selected_keywords.push(keyword.to_string());
- self.entry_table.entry_table_items = filtered_list;
- // Update scrollbar state with new lenght of itemlist
- self.entry_table.entry_scroll_state = ScrollbarState::content_length(
- self.entry_table.entry_scroll_state,
- self.entry_table.entry_table_items.len(),
- );
- self.filter_tags_by_entries();
- self.toggle_area();
- self.entry_table.entry_table_state.select(Some(0));
- self.former_area = Some(FormerArea::TagArea);
- }
-}
diff --git a/src/main.rs b/src/main.rs
index 979c4cf..eaa9e05 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,14 +15,15 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-use backend::cliargs::{self, CLIArgs};
+use cliargs::CLIArgs;
use color_eyre::eyre::Result;
use errorsetup::init_error_hooks;
-use frontend::app::App;
+use tui::app::App;
-pub mod backend;
+pub mod bib;
+pub mod cliargs;
pub mod errorsetup;
-pub mod frontend;
+pub mod tui;
#[tokio::main]
async fn main() -> Result<()> {
diff --git a/src/frontend/tui.rs b/src/tui.rs
index e3c9c1a..83d0b13 100644
--- a/src/frontend/tui.rs
+++ b/src/tui.rs
@@ -15,7 +15,12 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-use crate::frontend::app::App;
+pub mod app;
+pub mod command;
+pub mod handler;
+pub mod ui;
+
+use crate::tui::app::App;
use crossterm::{
cursor,
event::{
@@ -26,7 +31,7 @@ use crossterm::{
// use ratatui::backend::{Backend, CrosstermBackend};
use color_eyre::eyre::{OptionExt, Result};
use futures::{FutureExt, StreamExt};
-use ratatui::backend::CrosstermBackend as Backend;
+use ratatui::backend::CrosstermBackend;
use std::io::{stdout, Stdout};
use std::panic;
use std::{
@@ -52,7 +57,7 @@ pub enum Event {
#[derive(Debug)]
pub struct Tui {
/// Interface to the Terminal.
- pub terminal: ratatui::Terminal<Backend<Stdout>>,
+ pub terminal: ratatui::Terminal<CrosstermBackend<Stdout>>,
/// Event sender channel.
sender: mpsc::UnboundedSender<Event>,
/// Event receiver channel.
@@ -65,7 +70,7 @@ pub struct Tui {
impl Tui {
// Constructs a new instance of [`Tui`].
pub fn new() -> Result<Self> {
- let terminal = ratatui::Terminal::new(Backend::new(stdout()))?;
+ let terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
let (sender, receiver) = mpsc::unbounded_channel();
let handler = tokio::spawn(async {});
let cancellation_token = CancellationToken::new();
@@ -173,7 +178,7 @@ impl Tui {
pub fn exit(&mut self) -> Result<()> {
self.cancellation_token.cancel();
if crossterm::terminal::is_raw_mode_enabled()? {
- self.flush()?;
+ self.terminal.flush()?;
// if self.paste {
// crossterm::execute!(stdout(), DisableBracketedPaste)?;
// }
@@ -203,7 +208,7 @@ impl Tui {
}
impl Deref for Tui {
- type Target = ratatui::Terminal<Backend<Stdout>>;
+ type Target = ratatui::Terminal<CrosstermBackend<Stdout>>;
fn deref(&self) -> &Self::Target {
&self.terminal
diff --git a/src/frontend/app.rs b/src/tui/app.rs
index 822c6f0..b09ae80 100644
--- a/src/frontend/app.rs
+++ b/src/tui/app.rs
@@ -15,13 +15,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-use super::tui;
-use crate::backend::cliargs::CLIArgs;
-use crate::backend::{bib::*, search::BibiSearch};
-use crate::{
- frontend::entries::EntryTable, frontend::handler::handle_key_events,
- frontend::keywords::TagList, frontend::tui::Event,
-};
+use super::Event;
+use crate::bib::{bibmain::*, search::BibiSearch};
+use crate::cliargs::CLIArgs;
+use crate::tui;
+use crate::{bib::entries::EntryTable, bib::keywords::TagList, tui::handler::handle_key_events};
use arboard::Clipboard;
use color_eyre::eyre::{Ok, Result};
use std::path::PathBuf;
diff --git a/src/frontend/entries.rs b/src/tui/command.rs
index 7883a17..9f25f5f 100644
--- a/src/frontend/entries.rs
+++ b/src/tui/command.rs
@@ -15,196 +15,16 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-use super::app::App;
-use super::tui::Tui;
-use crate::backend::{bib::BibiData, search::BibiSearch};
+use crate::bib::entries::EntryTableColumn;
+use crate::bib::search::BibiSearch;
+use crate::tui::app::{App, FormerArea};
+use crate::tui::Tui;
use color_eyre::eyre::{Context, Ok, Result};
use core::panic;
use editor_command::EditorBuilder;
-use ratatui::widgets::{ScrollbarState, TableState};
+use ratatui::widgets::ScrollbarState;
use std::process::{Command, Stdio};
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum EntryTableColumn {
- Authors,
- Title,
- Year,
- Pubtype,
-}
-
-// Define list containing entries as table
-#[derive(Debug, PartialEq, Eq)]
-pub struct EntryTable {
- pub entry_table_items: Vec<EntryTableItem>,
- pub entry_table_at_search_start: Vec<EntryTableItem>,
- pub entry_table_selected_column: EntryTableColumn,
- pub entry_table_sorted_by_col: EntryTableColumn,
- pub entry_table_reversed_sort: bool,
- pub entry_table_state: TableState,
- pub entry_scroll_state: ScrollbarState,
- pub entry_info_scroll: u16,
- pub entry_info_scroll_state: ScrollbarState,
-}
-
-impl EntryTable {
- pub fn new(entry_list: Vec<BibiData>) -> Self {
- let entry_table_items = Self::set_entry_table(entry_list);
- let entry_table_state = TableState::default().with_selected(0);
- let entry_scroll_state = ScrollbarState::new(entry_table_items.len());
- let entry_info_scroll_state = ScrollbarState::default();
- Self {
- entry_table_items,
- entry_table_at_search_start: Vec::new(),
- entry_table_selected_column: EntryTableColumn::Authors,
- entry_table_sorted_by_col: EntryTableColumn::Authors,
- entry_table_reversed_sort: false,
- entry_table_state,
- entry_scroll_state,
- entry_info_scroll: 0,
- entry_info_scroll_state,
- }
- }
-
- pub fn set_entry_table(entry_list: Vec<BibiData>) -> Vec<EntryTableItem> {
- let mut entry_table: Vec<EntryTableItem> = entry_list
- .into_iter()
- .map(|e| EntryTableItem {
- authors: e.authors,
- short_author: String::new(),
- title: e.title,
- year: e.year,
- pubtype: e.pubtype,
- keywords: e.keywords,
- citekey: e.citekey,
- abstract_text: e.abstract_text,
- doi_url: e.doi_url,
- filepath: e.filepath,
- })
- .collect();
-
- entry_table.sort_by(|a, b| a.authors.to_lowercase().cmp(&b.authors.to_lowercase()));
- entry_table
- }
-
- // Sort entry table by specific column.
- // Toggle sorting by hitting same key again
- pub fn sort_entry_table(&mut self, toggle: bool) {
- if toggle {
- self.entry_table_reversed_sort = !self.entry_table_reversed_sort;
- }
- if self.entry_table_selected_column != self.entry_table_sorted_by_col {
- self.entry_table_reversed_sort = false
- }
- self.entry_table_sorted_by_col = self.entry_table_selected_column.clone();
- if self.entry_table_reversed_sort {
- match self.entry_table_selected_column {
- EntryTableColumn::Authors => self
- .entry_table_items
- .sort_by(|a, b| b.authors.to_lowercase().cmp(&a.authors.to_lowercase())),
- EntryTableColumn::Title => self
- .entry_table_items
- .sort_by(|a, b| b.title.to_lowercase().cmp(&a.title.to_lowercase())),
- EntryTableColumn::Year => self
- .entry_table_items
- .sort_by(|a, b| b.year.to_lowercase().cmp(&a.year.to_lowercase())),
- EntryTableColumn::Pubtype => self
- .entry_table_items
- .sort_by(|a, b| b.pubtype.to_lowercase().cmp(&a.pubtype.to_lowercase())),
- }
- } else if !self.entry_table_reversed_sort {
- match self.entry_table_selected_column {
- EntryTableColumn::Authors => self
- .entry_table_items
- .sort_by(|a, b| a.authors.to_lowercase().cmp(&b.authors.to_lowercase())),
- EntryTableColumn::Title => self
- .entry_table_items
- .sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase())),
- EntryTableColumn::Year => self
- .entry_table_items
- .sort_by(|a, b| a.year.to_lowercase().cmp(&b.year.to_lowercase())),
- EntryTableColumn::Pubtype => self
- .entry_table_items
- .sort_by(|a, b| a.pubtype.to_lowercase().cmp(&b.pubtype.to_lowercase())),
- }
- }
- }
-}
-
-// Define contents of each entry table row
-#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub struct EntryTableItem {
- pub authors: String,
- pub short_author: String,
- pub title: String,
- pub year: String,
- pub pubtype: String,
- pub keywords: String,
- pub citekey: String,
- pub abstract_text: String,
- pub doi_url: String,
- pub filepath: String,
-}
-
-impl EntryTableItem {
- // This functions decides which fields are rendered in the entry table
- // Fields which should be usable but not visible can be left out
- pub fn ref_vec(&mut self) -> Vec<&str> {
- self.short_author = match self.authors.split_once(",") {
- Some((first, _rest)) => {
- if self.authors.contains("(ed.)") {
- let first_author = format!("{} et al. (ed.)", first);
- first_author
- } else {
- let first_author = format!("{} et al.", first);
- first_author
- }
- }
- None => String::from(""),
- };
-
- vec![
- {
- if self.short_author.is_empty() {
- &self.authors
- } else {
- &self.short_author
- }
- },
- &self.title,
- &self.year,
- &self.pubtype,
- ]
- }
-
- pub fn authors(&self) -> &str {
- &self.authors
- }
-
- pub fn title(&self) -> &str {
- &self.title
- }
-
- pub fn year(&self) -> &str {
- &self.year
- }
-
- pub fn pubtype(&self) -> &str {
- &self.pubtype
- }
-
- pub fn citekey(&self) -> &str {
- &self.citekey
- }
-
- pub fn doi_url(&self) -> &str {
- &self.doi_url
- }
-
- pub fn filepath(&self) -> &str {
- &self.filepath
- }
-}
-
impl App {
// Entry Table commands
@@ -440,60 +260,104 @@ impl App {
}
}
-#[cfg(test)]
-mod tests {
- use super::EntryTableItem;
-
- #[test]
- fn check_os() {
- let os = std::env::consts::OS;
- assert_eq!(
- os,
- "linux",
- "You're not coding on linux, but on {}... Switch to linux, now!",
- std::env::consts::OS
- )
+impl App {
+ // Tag List commands
+
+ // Movement
+ pub fn select_next_tag(&mut self, keywords: u16) {
+ self.tag_list.tag_list_state.scroll_down_by(keywords);
+ self.tag_list.tag_scroll_state = self
+ .tag_list
+ .tag_scroll_state
+ .position(self.tag_list.tag_list_state.selected().unwrap());
}
- #[test]
- fn shorten_authors() {
- let mut entry: EntryTableItem = EntryTableItem {
- authors: "Miller, Schmitz, Bernard".to_string(),
- short_author: "".to_string(),
- title: "A title".to_string(),
- year: "2000".to_string(),
- pubtype: "article".to_string(),
- keywords: "key1, key2".to_string(),
- citekey: "miller_2000".to_string(),
- abstract_text: "An abstract".to_string(),
- doi_url: "www.text.org".to_string(),
- filepath: "/home/test".to_string(),
- };
+ pub fn select_previous_tag(&mut self, keywords: u16) {
+ self.tag_list.tag_list_state.scroll_up_by(keywords);
+ self.tag_list.tag_scroll_state = self
+ .tag_list
+ .tag_scroll_state
+ .position(self.tag_list.tag_list_state.selected().unwrap());
+ }
- let entry_vec = EntryTableItem::ref_vec(&mut entry);
-
- let mut entry_editors: EntryTableItem = EntryTableItem {
- authors: "Miller, Schmitz, Bernard (ed.)".to_string(),
- short_author: "".to_string(),
- title: "A title".to_string(),
- year: "2000".to_string(),
- pubtype: "article".to_string(),
- keywords: "key1, key2".to_string(),
- citekey: "miller_2000".to_string(),
- abstract_text: "An abstract".to_string(),
- doi_url: "www.text.org".to_string(),
- filepath: "/home/test".to_string(),
- };
+ pub fn select_first_tag(&mut self) {
+ self.tag_list.tag_list_state.select_first();
+ self.tag_list.tag_scroll_state = self.tag_list.tag_scroll_state.position(0);
+ }
- let entry_vec_editors = EntryTableItem::ref_vec(&mut entry_editors);
+ pub fn select_last_tag(&mut self) {
+ self.tag_list.tag_list_state.select_last();
+ self.tag_list.tag_scroll_state = self
+ .tag_list
+ .tag_scroll_state
+ .position(self.tag_list.tag_list_items.len());
+ }
+
+ pub fn get_selected_tag(&self) -> &str {
+ let idx = self.tag_list.tag_list_state.selected().unwrap();
+ let keyword = &self.tag_list.tag_list_items[idx];
+ // let keyword = &self.tag_list.tag_list_items[idx].keyword;
+ keyword
+ }
- assert_eq!(
- entry_vec,
- vec!["Miller et al.", "A title", "2000", "article"]
+ pub fn search_tags(&mut self) {
+ let orig_list = &self.main_biblio.keyword_list;
+ let filtered_list =
+ BibiSearch::search_tag_list(&self.search_struct.search_string, orig_list.clone());
+ self.tag_list.tag_list_items = filtered_list;
+ // Update scrollbar length after filtering list
+ self.tag_list.tag_scroll_state = ScrollbarState::content_length(
+ self.tag_list.tag_scroll_state,
+ self.tag_list.tag_list_items.len(),
+ );
+ }
+
+ pub fn filter_tags_by_entries(&mut self) {
+ let mut filtered_keywords: Vec<String> = Vec::new();
+
+ let orig_list = &self.entry_table.entry_table_items;
+
+ for e in orig_list {
+ if !e.keywords.is_empty() {
+ let mut key_vec: Vec<String> = e
+ .keywords
+ .split(',')
+ .map(|s| s.trim().to_string())
+ .filter(|s| !s.is_empty())
+ .collect();
+ filtered_keywords.append(&mut key_vec);
+ }
+ }
+
+ filtered_keywords.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
+ filtered_keywords.dedup();
+
+ self.search_struct.filtered_tag_list = filtered_keywords.clone();
+ self.tag_list.tag_list_items = filtered_keywords;
+ self.tag_list.tag_scroll_state = ScrollbarState::content_length(
+ self.tag_list.tag_scroll_state,
+ self.tag_list.tag_list_items.len(),
+ );
+ }
+
+ // Filter the entry list by tags when hitting enter
+ // If already inside a filtered tag or entry list, apply the filtering
+ // to the already filtered list only
+ pub fn filter_for_tags(&mut self) {
+ let orig_list = &self.entry_table.entry_table_items;
+ let keyword = self.get_selected_tag();
+ let filtered_list = BibiSearch::filter_entries_by_tag(&keyword, &orig_list);
+ // self.tag_list.selected_keyword = keyword.to_string();
+ self.tag_list.selected_keywords.push(keyword.to_string());
+ self.entry_table.entry_table_items = filtered_list;
+ // Update scrollbar state with new lenght of itemlist
+ self.entry_table.entry_scroll_state = ScrollbarState::content_length(
+ self.entry_table.entry_scroll_state,
+ self.entry_table.entry_table_items.len(),
);
- assert_eq!(
- entry_vec_editors,
- vec!["Miller et al. (ed.)", "A title", "2000", "article"]
- )
+ self.filter_tags_by_entries();
+ self.toggle_area();
+ self.entry_table.entry_table_state.select(Some(0));
+ self.former_area = Some(FormerArea::TagArea);
}
}
diff --git a/src/frontend/handler.rs b/src/tui/handler.rs
index 39ec7a2..5a196b5 100644
--- a/src/frontend/handler.rs
+++ b/src/tui/handler.rs
@@ -15,8 +15,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-use crate::frontend::app::App;
-use crate::frontend::tui::Tui;
+use crate::tui::app::App;
+use crate::tui::Tui;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use super::app::CurrentArea;
diff --git a/src/frontend/ui.rs b/src/tui/ui.rs
index 45ccd60..07bc88d 100644
--- a/src/frontend/ui.rs
+++ b/src/tui/ui.rs
@@ -15,8 +15,10 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-use color_eyre::owo_colors::OwoColorize;
-use itertools::Itertools;
+use super::app::{CurrentArea, FormerArea};
+use crate::bib::entries::EntryTableColumn;
+use crate::bib::keywords::TagListItem;
+use crate::tui::app::App;
use ratatui::{
buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect},
@@ -24,20 +26,11 @@ use ratatui::{
symbols,
text::{Line, Span, Text},
widgets::{
- block::{Position, Title},
Block, Borders, Cell, HighlightSpacing, List, ListItem, Padding, Paragraph, Row, Scrollbar,
ScrollbarOrientation, StatefulWidget, Table, Widget, Wrap,
},
};
-use crate::frontend::{app::App, keywords::TagListItem};
-
-use super::{
- app::{CurrentArea, FormerArea},
- entries::EntryTableColumn,
- keywords,
-};
-
const MAIN_BLUE_COLOR: Color = Color::Indexed(39);
// const MAIN_PURPLE_COLOR: Color = Color::Indexed(129);
const BOX_SELECTED_BOX_STYLE: Style = Style::new().fg(TEXT_FG_COLOR);
@@ -401,7 +394,7 @@ impl App {
.block(block)
.header(header)
.column_spacing(2)
- .highlight_style(SELECTED_STYLE)
+ .row_highlight_style(SELECTED_STYLE)
// .bg(Color::Black)
.highlight_spacing(HighlightSpacing::Always);
StatefulWidget::render(
@@ -550,8 +543,8 @@ impl App {
.block(
block
// Render arrows to show that info box has content outside the block
- .title(
- Title::from(
+ .title_bottom(
+ Line::from(
if box_height > area.height.into()
&& self.entry_table.entry_info_scroll
< box_height as u16 + 2 - area.height
@@ -561,12 +554,10 @@ impl App {
""
},
)
- .position(Position::Bottom)
.alignment(Alignment::Right),
)
- .title(
- Title::from(if scroll_height > 0 { " ▲ " } else { "" })
- .position(Position::Top)
+ .title_top(
+ Line::from(if scroll_height > 0 { " ▲ " } else { "" })
.alignment(Alignment::Right),
),
)