diff options
| author | lukeflo | 2024-10-22 21:52:36 +0200 |
|---|---|---|
| committer | lukeflo | 2024-10-22 21:52:36 +0200 |
| commit | 66402a9c23e0975a8a3d8c2707b689b9cde98ccf (patch) | |
| tree | ccba415674b13eadb6739f5a4d0cb53642dc2e62 | |
| parent | 0a74206015e764551ec2a0ade8f6853e915b6911 (diff) | |
| download | bibiman-66402a9c23e0975a8a3d8c2707b689b9cde98ccf.tar.gz bibiman-66402a9c23e0975a8a3d8c2707b689b9cde98ccf.zip | |
rearrange code, file and folder structure
| -rw-r--r-- | Cargo.lock | 24 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -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.rs | 258 | ||||
| -rw-r--r-- | src/bib/keywords.rs | 55 | ||||
| -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.rs | 23 | ||||
| -rw-r--r-- | src/frontend/keywords.rs | 159 | ||||
| -rw-r--r-- | src/main.rs | 9 | ||||
| -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
@@ -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" @@ -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), ), ) |
