aboutsummaryrefslogtreecommitdiff
path: root/src/frontend
diff options
context:
space:
mode:
authorlukeflo2024-10-19 23:06:19 +0200
committerlukeflo2024-10-19 23:06:19 +0200
commitf58527652547061e1349864c41d8daf5a1017a6e (patch)
tree36c6567f71ef7dd9db36a457227fbfdbb72fd2bb /src/frontend
parent5114820b9733bfb4daeedaef4f892ba9f5f023a0 (diff)
parentcf6a8d0c25bba1ee767c1dcc945cfbb577dbd13c (diff)
downloadbibiman-f58527652547061e1349864c41d8daf5a1017a6e.tar.gz
bibiman-f58527652547061e1349864c41d8daf5a1017a6e.zip
Merge branch 'value-parsing-and-hayagriva'
Sorting and other stuff implemented
Diffstat (limited to 'src/frontend')
-rw-r--r--src/frontend/app.rs12
-rw-r--r--src/frontend/entries.rs190
-rw-r--r--src/frontend/handler.rs3
-rw-r--r--src/frontend/keywords.rs32
-rw-r--r--src/frontend/tui.rs71
-rw-r--r--src/frontend/ui.rs45
6 files changed, 201 insertions, 152 deletions
diff --git a/src/frontend/app.rs b/src/frontend/app.rs
index 8d6454d..822c6f0 100644
--- a/src/frontend/app.rs
+++ b/src/frontend/app.rs
@@ -53,8 +53,6 @@ pub struct App {
pub main_bibfile: PathBuf,
// main bibliography
pub main_biblio: BibiMain,
- // bibliographic data
- pub biblio_data: BibiData,
// search struct:
pub search_struct: BibiSearch,
// tag list
@@ -76,16 +74,14 @@ impl App {
let running = true;
let main_bibfile = args.bibfilearg;
let main_biblio = BibiMain::new(main_bibfile.clone());
- let biblio_data = BibiData::new(&main_biblio.bibliography, &main_biblio.citekeys);
let tag_list = TagList::new(main_biblio.keyword_list.clone());
let search_struct = BibiSearch::default();
- let entry_table = EntryTable::from_iter(biblio_data.entry_list.bibentries.clone());
+ let entry_table = EntryTable::new(main_biblio.entry_list.clone());
let current_area = CurrentArea::EntryArea;
Ok(Self {
running,
main_bibfile,
main_biblio,
- biblio_data,
tag_list,
search_struct,
entry_table,
@@ -129,11 +125,9 @@ impl App {
pub fn update_lists(&mut self) {
self.main_biblio = BibiMain::new(self.main_bibfile.clone());
- self.biblio_data =
- BibiData::new(&self.main_biblio.bibliography, &self.main_biblio.citekeys);
// self.tag_list = TagList::from_iter(self.main_biblio.keyword_list.clone());
self.tag_list = TagList::new(self.main_biblio.keyword_list.clone());
- self.entry_table = EntryTable::from_iter(self.biblio_data.entry_list.bibentries.clone());
+ self.entry_table = EntryTable::new(self.main_biblio.entry_list.clone());
}
// Toggle moveable list between entries and tags
@@ -157,7 +151,7 @@ impl App {
}
pub fn reset_current_list(&mut self) {
- self.entry_table = EntryTable::from_iter(self.biblio_data.entry_list.bibentries.clone());
+ self.entry_table = EntryTable::new(self.main_biblio.entry_list.clone());
self.tag_list = TagList::new(self.main_biblio.keyword_list.clone());
if let CurrentArea::TagArea = self.current_area {
self.tag_list.tag_list_state.select(Some(0))
diff --git a/src/frontend/entries.rs b/src/frontend/entries.rs
index 98604f9..6c227df 100644
--- a/src/frontend/entries.rs
+++ b/src/frontend/entries.rs
@@ -17,11 +17,10 @@
use super::app::App;
use super::tui::Tui;
-use crate::backend::search::BibiSearch;
+use crate::backend::{bib::BibiData, search::BibiSearch};
use color_eyre::eyre::{Context, Ok, Result};
use core::panic;
use editor_command::EditorBuilder;
-use itertools::Itertools;
use ratatui::widgets::{ScrollbarState, TableState};
use std::process::{Command, Stdio};
@@ -30,44 +29,92 @@ use std::process::{Command, Stdio};
pub struct EntryTable {
pub entry_table_items: Vec<EntryTableItem>,
pub entry_table_at_search_start: Vec<EntryTableItem>,
+ 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 FromIterator<Vec<String>> for EntryTable {
- fn from_iter<T: IntoIterator<Item = Vec<String>>>(iter: T) -> Self {
- let entry_table_items: Vec<EntryTableItem> = iter
- .into_iter()
- .sorted()
- // 0: authors, 1: title, 2: date, 3: pubtype, 4: keywords, 5: citekey
- // 6: abstract, 7: doi/url, 8: pdf filepath
- // See backend/bib.rs BibiEntry impl
- .map(|i| {
- EntryTableItem::new(
- &i[0], &i[1], &i[2], &i[3], &i[4], &i[5], &i[6], &i[7], &i[8],
- )
- })
- .collect();
+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_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, sorting: &str, toggle: bool) {
+ if toggle {
+ self.entry_table_reversed_sort = !self.entry_table_reversed_sort;
+ }
+ if self.entry_table_reversed_sort {
+ match sorting {
+ "author" => self
+ .entry_table_items
+ .sort_by(|a, b| b.authors.to_lowercase().cmp(&a.authors.to_lowercase())),
+ "title" => self
+ .entry_table_items
+ .sort_by(|a, b| b.title.to_lowercase().cmp(&a.title.to_lowercase())),
+ "year" => self
+ .entry_table_items
+ .sort_by(|a, b| b.year.to_lowercase().cmp(&a.year.to_lowercase())),
+ _ => {}
+ }
+ } else if !self.entry_table_reversed_sort {
+ match sorting {
+ "author" => self
+ .entry_table_items
+ .sort_by(|a, b| a.authors.to_lowercase().cmp(&b.authors.to_lowercase())),
+ "title" => self
+ .entry_table_items
+ .sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase())),
+ "year" => self
+ .entry_table_items
+ .sort_by(|a, b| a.year.to_lowercase().cmp(&b.year.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,
@@ -79,34 +126,34 @@ pub struct EntryTableItem {
}
impl EntryTableItem {
- pub fn new(
- authors: &str,
- title: &str,
- year: &str,
- pubtype: &str,
- keywords: &str,
- citekey: &str,
- abstract_text: &str,
- doi_url: &str,
- filepath: &str,
- ) -> Self {
- Self {
- authors: authors.to_string(),
- title: title.to_string(),
- year: year.to_string(),
- pubtype: pubtype.to_string(),
- keywords: keywords.to_string(),
- citekey: citekey.to_string(),
- abstract_text: abstract_text.to_string(),
- doi_url: doi_url.to_string(),
- filepath: filepath.to_string(),
- }
- }
-
// 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(&self) -> Vec<&String> {
- vec![&self.authors, &self.title, &self.year, &self.pubtype]
+ 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 {
@@ -128,6 +175,14 @@ impl EntryTableItem {
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 {
@@ -256,6 +311,13 @@ impl App {
let filtered_list =
BibiSearch::search_entry_list(&mut self.search_struct.search_string, orig_list.clone());
self.entry_table.entry_table_items = filtered_list;
+ if self.entry_table.entry_table_reversed_sort {
+ self.entry_table.sort_entry_table("author", false);
+ }
+ self.entry_table.entry_scroll_state = ScrollbarState::content_length(
+ self.entry_table.entry_scroll_state,
+ self.entry_table.entry_table_items.len(),
+ );
}
// Open file connected with entry through 'file' or 'pdf' field
@@ -326,6 +388,8 @@ impl App {
#[cfg(test)]
mod tests {
+ use super::EntryTableItem;
+
#[test]
fn check_os() {
let os = std::env::consts::OS;
@@ -336,4 +400,46 @@ mod tests {
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/frontend/handler.rs b/src/frontend/handler.rs
index c2cacf5..ec1647e 100644
--- a/src/frontend/handler.rs
+++ b/src/frontend/handler.rs
@@ -121,6 +121,9 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App, tui: &mut Tui) -> R
KeyCode::Char('G') | KeyCode::End => {
app.select_last_entry();
}
+ KeyCode::Char('s') => {
+ app.entry_table.sort_entry_table("author", true);
+ }
KeyCode::Char('y') => {
App::yank_text(&app.get_selected_citekey());
}
diff --git a/src/frontend/keywords.rs b/src/frontend/keywords.rs
index 9432752..ba74b02 100644
--- a/src/frontend/keywords.rs
+++ b/src/frontend/keywords.rs
@@ -42,23 +42,6 @@ impl TagListItem {
}
}
-// impl FromIterator<String> for TagList {
-// fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
-// let tag_list_items: Vec<TagListItem> = iter
-// .into_iter()
-// .map(|info| TagListItem::new(&info))
-// .collect();
-// 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_keyword: String::new(),
-// }
-// }
-// }
-
impl TagList {
pub fn new(keyword_list: Vec<String>) -> Self {
let tag_list_items = keyword_list;
@@ -118,7 +101,11 @@ impl App {
let filtered_list =
BibiSearch::search_tag_list(&self.search_struct.search_string, orig_list.clone());
self.tag_list.tag_list_items = filtered_list;
- // self.tag_list = TagList::from_iter(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) {
@@ -143,6 +130,10 @@ impl App {
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
@@ -154,6 +145,11 @@ impl App {
let filtered_list = BibiSearch::filter_entries_by_tag(&keyword, &orig_list);
self.tag_list.selected_keyword = 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));
diff --git a/src/frontend/tui.rs b/src/frontend/tui.rs
index 698407d..e3c9c1a 100644
--- a/src/frontend/tui.rs
+++ b/src/frontend/tui.rs
@@ -24,6 +24,8 @@ use crossterm::{
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
};
// use ratatui::backend::{Backend, CrosstermBackend};
+use color_eyre::eyre::{OptionExt, Result};
+use futures::{FutureExt, StreamExt};
use ratatui::backend::CrosstermBackend as Backend;
use std::io::{stdout, Stdout};
use std::panic;
@@ -31,10 +33,6 @@ use std::{
ops::{Deref, DerefMut},
time::Duration,
};
-
-use color_eyre::config::HookBuilder;
-use color_eyre::eyre::{OptionExt, Result};
-use futures::{FutureExt, StreamExt};
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
@@ -51,14 +49,6 @@ pub enum Event {
Resize(u16, u16),
}
-// pub type IO = std::io::{{crossterm_io | title_case}};
-// pub fn io() -> IO {
-// std::io::{{crossterm_io}}()
-// }
-/// Representation of a terminal user interface.
-///
-/// It is responsible for setting up the terminal,
-/// initializing the interface and handling the draw events.
#[derive(Debug)]
pub struct Tui {
/// Interface to the Terminal.
@@ -73,7 +63,7 @@ pub struct Tui {
}
impl Tui {
- /// Constructs a new instance of [`Tui`].
+ // Constructs a new instance of [`Tui`].
pub fn new() -> Result<Self> {
let terminal = ratatui::Terminal::new(Backend::new(stdout()))?;
let (sender, receiver) = mpsc::unbounded_channel();
@@ -150,26 +140,6 @@ impl Tui {
cancellation_token.cancel();
}
- /// Initializes the terminal interface.
- ///
- /// It enables the raw mode and sets terminal properties.
- // pub fn init(&mut self) -> Result<()> {
- // terminal::enable_raw_mode()?;
- // crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
-
- // // Define a custom panic hook to reset the terminal properties.
- // // This way, you won't have your terminal messed up if an unexpected error happens.
- // let panic_hook = panic::take_hook();
- // panic::set_hook(Box::new(move |panic| {
- // Self::reset().expect("failed to reset the terminal");
- // panic_hook(panic);
- // }));
-
- // self.terminal.hide_cursor()?;
- // self.terminal.clear()?;
- // Ok(())
- // }
-
pub fn enter(&mut self) -> Result<()> {
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(stdout(), EnterAlternateScreen, cursor::Hide)?;
@@ -216,10 +186,10 @@ impl Tui {
Ok(())
}
- /// [`Draw`] the terminal interface by [`rendering`] the widgets.
- ///
- /// [`Draw`]: ratatui::Terminal::draw
- /// [`rendering`]: crate::ui::render
+ // [`Draw`] the terminal interface by [`rendering`] the widgets.
+ //
+ // [`Draw`]: ratatui::Terminal::draw
+ // [`rendering`]: crate::ui::render
pub fn draw(&mut self, app: &mut App) -> Result<()> {
// self.terminal.draw(|frame| ui::render(app, frame))?;
self.terminal
@@ -230,33 +200,6 @@ impl Tui {
pub async fn next(&mut self) -> Result<Event> {
self.receiver.recv().await.ok_or_eyre("This is an IO error")
}
-
- pub fn init_error_hooks() -> Result<()> {
- let (panic, error) = HookBuilder::default().into_hooks();
- let panic = panic.into_panic_hook();
- let error = error.into_eyre_hook();
- color_eyre::eyre::set_hook(Box::new(move |e| {
- let _ = crossterm::execute!(
- stdout(),
- DisableMouseCapture,
- LeaveAlternateScreen,
- cursor::Show
- );
- let _ = crossterm::terminal::disable_raw_mode();
- error(e)
- }))?;
- std::panic::set_hook(Box::new(move |info| {
- let _ = crossterm::execute!(
- stdout(),
- DisableMouseCapture,
- LeaveAlternateScreen,
- cursor::Show
- );
- let _ = crossterm::terminal::disable_raw_mode();
- panic(info)
- }));
- Ok(())
- }
}
impl Deref for Tui {
diff --git a/src/frontend/ui.rs b/src/frontend/ui.rs
index 08cebcb..4ea275d 100644
--- a/src/frontend/ui.rs
+++ b/src/frontend/ui.rs
@@ -28,7 +28,7 @@ use ratatui::{
},
};
-use crate::{frontend::app::App, frontend::keywords::TagListItem};
+use crate::frontend::{app::App, keywords::TagListItem};
use super::app::{CurrentArea, FormerArea};
@@ -47,6 +47,8 @@ const SELECTED_STYLE: Style = Style::new()
.add_modifier(Modifier::REVERSED);
const TEXT_FG_COLOR: Color = Color::Indexed(252);
const TEXT_UNSELECTED_FG_COLOR: Color = Color::Indexed(245);
+const SORTED_ENTRIES: &str = "▼";
+const SORTED_ENTRIES_REVERSED: &str = "▲";
const SCROLLBAR_UPPER_CORNER: Option<&str> = Some("┓");
const SCROLLBAR_LOWER_CORNER: Option<&str> = Some("┛");
@@ -259,15 +261,22 @@ impl App {
let header_style = Style::default().bold().fg(TEXT_FG_COLOR);
- let header = [
- "Authors".underlined(),
- "Title".underlined(),
- "Year".underlined(),
- "Type".underlined(),
- ]
- .into_iter()
- .map(Cell::from)
- .collect::<Row>()
+ let header = Row::new(vec![
+ Cell::from(Line::from(vec![
+ Span::raw("Author").underlined(),
+ Span::raw(format!(
+ " {}",
+ if self.entry_table.entry_table_reversed_sort {
+ SORTED_ENTRIES_REVERSED
+ } else {
+ SORTED_ENTRIES
+ }
+ )),
+ ])),
+ Cell::from("Title".to_string().underlined()),
+ Cell::from("Year".to_string().underlined()),
+ Cell::from("Type".to_string().underlined()),
+ ])
.style(header_style)
.height(1);
@@ -275,7 +284,7 @@ impl App {
let rows = self
.entry_table
.entry_table_items
- .iter()
+ .iter_mut()
.enumerate()
.map(|(_i, data)| {
let item = data.ref_vec();
@@ -340,15 +349,16 @@ impl App {
let mut lines = vec![];
lines.push(Line::from(vec![
Span::styled("Authors: ", style_value),
- Span::styled(cur_entry.authors.clone(), Style::new().green()),
+ // Span::styled(cur_entry.authors.clone(), Style::new().green()),
+ Span::styled(cur_entry.authors(), Style::new().green()),
]));
lines.push(Line::from(vec![
Span::styled("Title: ", style_value),
- Span::styled(cur_entry.title.clone(), Style::new().magenta()),
+ Span::styled(cur_entry.title(), Style::new().magenta()),
]));
lines.push(Line::from(vec![
Span::styled("Year: ", style_value),
- Span::styled(cur_entry.year.clone(), Style::new().light_magenta()),
+ Span::styled(cur_entry.year(), Style::new().light_magenta()),
]));
if !cur_entry.doi_url.is_empty() || !cur_entry.filepath.is_empty() {
lines.push(Line::raw(""));
@@ -357,7 +367,7 @@ impl App {
lines.push(Line::from(vec![
Span::styled("DOI/URL: ", style_value_sec),
Span::styled(
- cur_entry.doi_url.clone(),
+ cur_entry.doi_url(),
Style::default().fg(TEXT_FG_COLOR).underlined(),
),
]));
@@ -365,10 +375,7 @@ impl App {
if !cur_entry.filepath.is_empty() {
lines.push(Line::from(vec![
Span::styled("File: ", style_value_sec),
- Span::styled(
- cur_entry.filepath.clone(),
- Style::default().fg(TEXT_FG_COLOR),
- ),
+ Span::styled(cur_entry.filepath(), Style::default().fg(TEXT_FG_COLOR)),
]));
}
lines.push(Line::from(""));