aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/bib.rs61
-rw-r--r--src/backend/cliargs.rs2
-rw-r--r--src/backend/search.rs173
-rw-r--r--src/frontend/app.rs86
-rw-r--r--src/frontend/handler.rs52
-rw-r--r--src/frontend/ui.rs30
6 files changed, 300 insertions, 104 deletions
diff --git a/src/backend/bib.rs b/src/backend/bib.rs
index cbaa00e..c897099 100644
--- a/src/backend/bib.rs
+++ b/src/backend/bib.rs
@@ -29,7 +29,7 @@ pub struct BibiMain {
pub bibfilestring: String, // content of bibfile as string
pub bibliography: Bibliography, // parsed bibliography
pub citekeys: Vec<String>, // list of all citekeys
- // pub bibentries: BibiDataSets,
+ pub keyword_list: Vec<String>, // list of all available keywords
}
impl BibiMain {
@@ -39,11 +39,13 @@ impl BibiMain {
let bibfilestring = fs::read_to_string(&bibfile).unwrap();
let bibliography = biblatex::Bibliography::parse(&bibfilestring).unwrap();
let citekeys = Self::get_citekeys(&bibliography);
+ let keyword_list = Self::collect_tag_list(&citekeys, &bibliography);
Self {
bibfile,
bibfilestring,
bibliography,
citekeys,
+ keyword_list,
}
}
@@ -56,6 +58,40 @@ impl BibiMain {
citekeys.sort_by_key(|name| name.to_lowercase());
citekeys
}
+
+ // collect all keywords present in the bibliography
+ // sort them and remove duplicates
+ // this list is for fast filtering entries by topics/keyowrds
+ pub fn collect_tag_list(citekeys: &Vec<String>, biblio: &Bibliography) -> Vec<String> {
+ // Initialize vector collecting all keywords
+ let mut keyword_list = vec![];
+
+ // Loop over entries and collect all keywords
+ for i in citekeys {
+ if biblio.get(&i).unwrap().keywords().is_ok() {
+ let items = biblio
+ .get(&i)
+ .unwrap()
+ .keywords()
+ .unwrap()
+ .format_verbatim();
+ // Split keyword string into slices, trim leading and trailing
+ // whitespaces, remove empty slices, and collect them
+ let mut key_vec: Vec<String> = items
+ .split(',')
+ .map(|s| s.trim().to_string())
+ .filter(|s| !s.is_empty())
+ .collect();
+ // Append keywords to vector
+ keyword_list.append(&mut key_vec);
+ }
+ }
+
+ // Sort the vector and remove duplicates
+ keyword_list.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
+ keyword_list.dedup();
+ keyword_list
+ }
}
#[derive(Debug)]
@@ -91,7 +127,7 @@ pub struct BibiEntry {
pub title: String,
pub year: String,
pub pubtype: String,
- // pub keywords: Vec<String>,
+ pub keywords: String,
pub citekey: String,
}
@@ -102,6 +138,7 @@ impl BibiEntry {
Self::get_title(&citekey, &biblio),
Self::get_year(&citekey, &biblio),
Self::get_pubtype(&citekey, &biblio),
+ Self::get_keywords(&citekey, &biblio),
citekey.to_string(),
]
}
@@ -181,12 +218,20 @@ impl BibiEntry {
}
pub fn get_keywords(citekey: &str, biblio: &Bibliography) -> String {
- let keywords = biblio
- .get(&citekey)
- .unwrap()
- .keywords()
- .unwrap()
- .format_verbatim();
+ let keywords = {
+ if biblio.get(&citekey).unwrap().keywords().is_ok() {
+ let keywords = biblio
+ .get(&citekey)
+ .unwrap()
+ .keywords()
+ .unwrap()
+ .format_verbatim();
+ keywords
+ } else {
+ let keywords = String::from("");
+ keywords
+ }
+ };
keywords
}
diff --git a/src/backend/cliargs.rs b/src/backend/cliargs.rs
index 32972e0..516de66 100644
--- a/src/backend/cliargs.rs
+++ b/src/backend/cliargs.rs
@@ -16,7 +16,7 @@
/////
use core::panic;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
use sarge::prelude::*;
diff --git a/src/backend/search.rs b/src/backend/search.rs
index c3b5816..d09dd04 100644
--- a/src/backend/search.rs
+++ b/src/backend/search.rs
@@ -4,50 +4,145 @@ use nucleo_matcher::{
};
use std::collections::HashMap;
-// Stringify inner Vec<String> by joining/concat
-fn convert_to_string(inner_vec: &Vec<String>) -> String {
- inner_vec.join(" ")
+#[derive(Debug)]
+pub struct BibiSearch {
+ pub search_string: String, // Search string show in footer, used for search
+ pub inner_search: bool, // True, if we trigger a search for already filtered list
+ pub filtered_entry_list: Vec<Vec<String>>, // Temporary holds filtered entry list to refilter it
}
-// 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();
+impl Default for BibiSearch {
+ fn default() -> Self {
+ Self {
+ search_string: String::new(),
+ inner_search: false,
+ filtered_entry_list: Vec::new(),
+ }
+ }
+}
+
+impl BibiSearch {
+ // 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(Self::convert_to_string(&entry), entry);
+ }
- // 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
}
- // 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());
+ pub fn search_tag_list(search_pattern: &str, orig_list: Vec<String>) -> Vec<String> {
+ // Set up matcher (TODO: One time needed only)
+ let mut matcher = Matcher::new(Config::DEFAULT);
+
+ // Filter the list items by search pattern
+ let filtered_matches: Vec<String> = {
+ let matches =
+ Pattern::parse(search_pattern, CaseMatching::Ignore, Normalization::Smart)
+ .match_list(orig_list, &mut matcher);
+ matches.into_iter().map(|f| f.0.to_string()).collect()
+ };
+ filtered_matches
}
- filtered_list
-}
-pub fn search_tag_list(search_pattern: &str, orig_list: Vec<String>) -> Vec<String> {
- // Set up matcher (TODO: One time needed only)
- let mut matcher = Matcher::new(Config::DEFAULT);
-
- // Filter the list items by search pattern
- let filtered_matches: Vec<String> = {
- let matches = Pattern::parse(search_pattern, CaseMatching::Ignore, Normalization::Smart)
- .match_list(orig_list, &mut matcher);
- matches.into_iter().map(|f| f.0.to_string()).collect()
- };
- filtered_matches
+ pub fn filter_entries_by_tag(keyword: &str, orig_list: &Vec<Vec<String>>) -> Vec<Vec<String>> {
+ let mut filtered_list: Vec<Vec<String>> = Vec::new();
+
+ for e in orig_list {
+ if e[4].contains(keyword) {
+ filtered_list.push(e.to_owned());
+ }
+ }
+
+ filtered_list
+ }
}
+// // 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
+// }
+
+// pub fn search_tag_list(search_pattern: &str, orig_list: Vec<String>) -> Vec<String> {
+// // Set up matcher (TODO: One time needed only)
+// let mut matcher = Matcher::new(Config::DEFAULT);
+
+// // Filter the list items by search pattern
+// let filtered_matches: Vec<String> = {
+// let matches = Pattern::parse(search_pattern, CaseMatching::Ignore, Normalization::Smart)
+// .match_list(orig_list, &mut matcher);
+// matches.into_iter().map(|f| f.0.to_string()).collect()
+// };
+// filtered_matches
+// }
+
+// pub fn filter_entries_by_tag(keyword: &str, orig_list: &Vec<Vec<String>>) -> Vec<Vec<String>> {
+// let mut filtered_list: Vec<Vec<String>> = Vec::new();
+
+// for e in orig_list {
+// if e[4].contains(keyword) {
+// filtered_list.push(e.to_owned());
+// }
+// }
+
+// filtered_list
+// }
diff --git a/src/frontend/app.rs b/src/frontend/app.rs
index 1105901..ed0318f 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::*, search};
+use crate::backend::{bib::*, search::BibiSearch};
use std::error;
use arboard::Clipboard;
@@ -51,6 +51,8 @@ pub struct App {
pub main_biblio: BibiMain,
// bibliographic data
pub biblio_data: BibiData,
+ // search struct:
+ pub search_struct: BibiSearch,
// tag list
pub tag_list: TagList,
// table items
@@ -62,7 +64,7 @@ pub struct App {
// mode for popup window
pub former_area: Option<FormerArea>,
// search string
- pub search_string: String,
+ // pub search_string: String,
}
// Define the fundamental List
@@ -75,14 +77,14 @@ pub struct TagList {
// Structure of the list items.
#[derive(Debug)]
pub struct TagListItem {
- pub info: String,
+ 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 {
- info: info.to_string(),
+ keyword: info.to_string(),
}
}
}
@@ -107,7 +109,7 @@ impl FromIterator<Vec<String>> for EntryTable {
let entry_table_items = iter
.into_iter()
.sorted()
- .map(|i| EntryTableItem::new(&i[0], &i[1], &i[2], &i[3], &i[4]))
+ .map(|i| EntryTableItem::new(&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]))
.collect();
let entry_table_state = TableState::default().with_selected(0);
Self {
@@ -131,17 +133,26 @@ pub struct EntryTableItem {
pub title: String,
pub year: String,
pub pubtype: String,
+ pub keywords: String,
pub citekey: String,
// pub year: u16,
}
impl EntryTableItem {
- pub fn new(authors: &str, title: &str, year: &str, pubtype: &str, citekey: &str) -> Self {
+ pub fn new(
+ authors: &str,
+ title: &str,
+ year: &str,
+ pubtype: &str,
+ keywords: &str,
+ citekey: &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(),
}
}
@@ -178,7 +189,8 @@ impl Default for App {
let running = true;
let main_biblio = BibiMain::new();
let biblio_data = BibiData::new(&main_biblio.bibliography, &main_biblio.citekeys);
- let tag_list = TagList::from_iter(main_biblio.citekeys.clone());
+ let tag_list = TagList::from_iter(main_biblio.keyword_list.clone());
+ let search_struct = BibiSearch::default();
let entry_table = EntryTable::from_iter(biblio_data.entry_list.bibentries.clone());
let current_area = CurrentArea::EntryArea;
Self {
@@ -186,11 +198,12 @@ impl Default for App {
main_biblio,
biblio_data,
tag_list,
+ search_struct,
entry_table,
scroll_info: 0,
current_area,
former_area: None,
- search_string: String::new(),
+ // search_string: String::new(),
}
}
}
@@ -290,8 +303,40 @@ impl App {
self.tag_list.tag_list_state.select_last();
}
+ 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].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 = TagList::from_iter(filtered_list)
+ }
+
pub fn reset_taglist(&mut self) {
- self.tag_list = TagList::from_iter(self.main_biblio.citekeys.clone())
+ self.tag_list = TagList::from_iter(self.main_biblio.keyword_list.clone())
+ }
+
+ // Filter the entry list by tags
+ // 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 = {
+ if self.search_struct.inner_search {
+ let orig_list = &self.search_struct.filtered_entry_list;
+ orig_list
+ } else {
+ let orig_list = &self.biblio_data.entry_list.bibentries;
+ orig_list
+ }
+ };
+ let keyword = self.get_selected_tag();
+ let filtered_list = BibiSearch::filter_entries_by_tag(&keyword, &orig_list);
+ self.search_struct.filtered_entry_list = filtered_list;
+ self.entry_table = EntryTable::from_iter(self.search_struct.filtered_entry_list.clone());
}
// Entry Table commands
@@ -331,19 +376,18 @@ impl App {
// Search entry list
pub fn search_entries(&mut self) {
- match self.former_area {
- Some(FormerArea::EntryArea) => {
+ let orig_list = {
+ if self.search_struct.inner_search {
+ let orig_list = &self.search_struct.filtered_entry_list;
+ orig_list
+ } else {
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)
- }
- Some(FormerArea::TagArea) => {
- let orig_list = &self.main_biblio.citekeys;
- let filtered_list = search::search_tag_list(&self.search_string, orig_list.clone());
- self.tag_list = TagList::from_iter(filtered_list)
+ orig_list
}
- _ => {}
- }
+ };
+ let filtered_list =
+ BibiSearch::search_entry_list(&mut self.search_struct.search_string, orig_list.clone());
+ //search::search_entry_list(&self.search_string, orig_list.clone());
+ self.entry_table = EntryTable::from_iter(filtered_list)
}
}
diff --git a/src/frontend/handler.rs b/src/frontend/handler.rs
index c3eeed5..cdbd981 100644
--- a/src/frontend/handler.rs
+++ b/src/frontend/handler.rs
@@ -15,10 +15,13 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
-use crate::frontend::app::{App, AppResult};
+use crate::{
+ backend::search::BibiSearch,
+ frontend::app::{App, AppResult},
+};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
-use super::app::{CurrentArea, EntryTable, FormerArea};
+use super::app::{CurrentArea, FormerArea};
/// Handles the key events and updates the state of [`App`].
pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
@@ -61,14 +64,19 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
KeyCode::Char('/') => {
app.former_area = Some(FormerArea::TagArea);
app.current_area = CurrentArea::SearchArea;
+ // app.search_struct.is_search = true;
}
KeyCode::Tab | KeyCode::BackTab => {
app.toggle_area();
}
KeyCode::Esc => {
- if let Some(FormerArea::SearchArea) = app.former_area {
- app.reset_taglist();
- }
+ app.reset_taglist();
+ }
+ KeyCode::Enter => {
+ app.filter_for_tags();
+ app.toggle_area();
+ // app.reset_taglist();
+ app.former_area = Some(FormerArea::TagArea);
}
_ => {}
},
@@ -90,6 +98,9 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
App::yank_text(&app.get_selected_citekey());
}
KeyCode::Char('/') => {
+ if let Some(FormerArea::TagArea) = app.former_area {
+ app.search_struct.inner_search = true;
+ }
app.former_area = Some(FormerArea::EntryArea);
app.current_area = CurrentArea::SearchArea;
}
@@ -97,9 +108,11 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
app.toggle_area();
}
KeyCode::Esc => {
- if let Some(FormerArea::SearchArea) = app.former_area {
- app.reset_entry_table();
+ app.reset_entry_table();
+ if app.search_struct.inner_search {
+ app.reset_taglist();
}
+ app.former_area = None;
}
_ => {}
},
@@ -113,21 +126,34 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
app.reset_taglist();
}
app.former_area = None;
- app.search_string.clear();
+ // If search is canceled, reset default status of struct
+ BibiSearch::default();
+ // app.search_struct.search_string.clear();
+ // app.search_struct.is_search = false;
+ // app.search_struct.filtered_entry_list.clear();
}
KeyCode::Enter => {
// TODO: run function for filtering the list
app.toggle_area();
app.former_area = Some(FormerArea::SearchArea);
- app.search_string.clear();
+ // app.search_string.clear();
+ app.search_struct.search_string.clear();
}
KeyCode::Backspace => {
- app.search_string.pop();
- app.search_entries();
+ app.search_struct.search_string.pop();
+ if let Some(FormerArea::EntryArea) = app.former_area {
+ app.search_entries();
+ } else if let Some(FormerArea::TagArea) = app.former_area {
+ app.search_tags();
+ }
}
KeyCode::Char(search_pattern) => {
- app.search_string.push(search_pattern);
- app.search_entries();
+ app.search_struct.search_string.push(search_pattern);
+ if let Some(FormerArea::EntryArea) = app.former_area {
+ app.search_entries();
+ } else if let Some(FormerArea::TagArea) = app.former_area {
+ app.search_tags();
+ }
}
_ => {}
},
diff --git a/src/frontend/ui.rs b/src/frontend/ui.rs
index 0829227..7323917 100644
--- a/src/frontend/ui.rs
+++ b/src/frontend/ui.rs
@@ -18,15 +18,12 @@
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
- style::{
- palette::tailwind::{GRAY, SLATE},
- Color, Modifier, Style, Stylize,
- },
+ style::{palette::tailwind::SLATE, Color, Modifier, Style, Stylize},
symbols,
text::{Line, Span, Text},
widgets::{
- Block, Borders, Cell, HighlightSpacing, List, ListItem, Padding, Paragraph, Row,
- StatefulWidget, Table, Widget, Wrap,
+ Block, Cell, HighlightSpacing, List, ListItem, Padding, Paragraph, Row, StatefulWidget,
+ Table, Widget, Wrap,
},
};
@@ -38,7 +35,7 @@ use crate::{
use super::app::{CurrentArea, FormerArea};
const MAIN_BLUE_COLOR: Color = Color::Indexed(39);
-const MAIN_PURPLE_COLOR: Color = Color::Indexed(129);
+// const MAIN_PURPLE_COLOR: Color = Color::Indexed(129);
const BOX_BORDER_STYLE_MAIN: Style = Style::new().fg(Color::White).bg(Color::Black);
const NORMAL_ROW_BG: Color = Color::Black;
const ALT_ROW_BG_COLOR: Color = Color::Indexed(234);
@@ -47,7 +44,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);
+// const TEXT_CONFIRMED: Style = Style::new().fg(Color::Green);
pub const fn alternate_colors(i: usize) -> Color {
if i % 2 == 0 {
@@ -59,13 +56,7 @@ pub const fn alternate_colors(i: usize) -> Color {
impl From<&TagListItem> for ListItem<'_> {
fn from(value: &TagListItem) -> Self {
- let line = Line::styled(format!("{}", value.info), TEXT_FG_COLOR);
- // match value.status {
- // Status::Todo => Line::styled(format!(" ☐ {}", value.todo), TEXT_FG_COLOR),
- // Status::Completed => {
- // Line::styled(format!(" ✓ {}", value.todo), COMPLETED_TEXT_FG_COLOR)
- // }
- // };
+ let line = Line::styled(format!("{}", value.keyword), TEXT_FG_COLOR);
ListItem::new(line)
}
}
@@ -129,7 +120,7 @@ impl App {
let block = Block::bordered()
.title(search_title)
.border_set(symbols::border::ROUNDED);
- Paragraph::new(self.search_string.clone())
+ Paragraph::new(self.search_struct.search_string.clone())
.block(block)
.render(area, buf);
}
@@ -149,12 +140,7 @@ impl App {
pub fn render_entrytable(&mut self, area: Rect, buf: &mut Buffer) {
let block = Block::bordered() // can also be Block::new
- .title(
- Line::raw(" Bibliographic Entries ")
- .centered()
- .bold()
- .fg(Color::Indexed(39)),
- )
+ .title(Line::raw(" Bibliographic Entries ").centered().bold())
.border_set(symbols::border::ROUNDED)
.border_style(BOX_BORDER_STYLE_MAIN)
.bg(Color::Black); // .bg(NORMAL_ROW_BG);