// 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 . ///// use crate::backend::{bib::*, search}; use std::error; use arboard::Clipboard; use itertools::Itertools; use ratatui::widgets::{ListState, TableState}; // Application result type. pub type AppResult = std::result::Result>; // Areas in which actions are possible #[derive(Debug)] pub enum CurrentArea { EntryArea, TagArea, SearchArea, HelpArea, } // Check which area was active when popup set active #[derive(Debug)] pub enum FormerArea { EntryArea, TagArea, SearchArea, } // Application. #[derive(Debug)] pub struct App { // Is the application running? pub running: bool, // main bibliography pub main_biblio: BibiMain, // bibliographic data pub biblio_data: BibiData, // tag list pub tag_list: TagList, // table items pub entry_table: EntryTable, // scroll state info buffer pub scroll_info: u16, // area pub current_area: CurrentArea, // mode for popup window pub former_area: Option, // search string pub search_string: String, } // Define the fundamental List #[derive(Debug)] pub struct TagList { pub tag_list_items: Vec, pub tag_list_state: ListState, } // Structure of the list items. #[derive(Debug)] pub struct TagListItem { pub info: 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(), } } } impl FromIterator for TagList { fn from_iter>(iter: I) -> Self { let tag_list_items = iter .into_iter() .map(|info| TagListItem::new(&info)) .collect(); let tag_list_state = ListState::default(); // for preselection: .with_selected(Some(0)); Self { tag_list_items, tag_list_state, } } } // // Also possible with vector. impl FromIterator> for EntryTable { fn from_iter>>(iter: T) -> Self { let entry_table_items = iter .into_iter() .sorted() .map(|i| EntryTableItem::new(&i[0], &i[1], &i[2], &i[3], &i[4])) .collect(); let entry_table_state = TableState::default().with_selected(0); Self { entry_table_items, entry_table_state, } } } // Define list containing entries as table #[derive(Debug)] pub struct EntryTable { pub entry_table_items: Vec, pub entry_table_state: TableState, } // Define contents of each entry table row #[derive(Debug)] pub struct EntryTableItem { pub authors: String, pub title: String, pub year: String, pub pubtype: String, pub citekey: String, // pub year: u16, } impl EntryTableItem { pub fn new(authors: &str, title: &str, year: &str, pubtype: &str, citekey: &str) -> Self { Self { authors: authors.to_string(), title: title.to_string(), year: year.to_string(), pubtype: pubtype.to_string(), citekey: citekey.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 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 } } impl Default for App { fn default() -> Self { 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 entry_table = EntryTable::from_iter(biblio_data.entry_list.bibentries.clone()); let current_area = CurrentArea::EntryArea; Self { running, main_biblio, biblio_data, tag_list, entry_table, scroll_info: 0, current_area, former_area: None, search_string: String::new(), } } } impl App { // Constructs a new instance of [`App`]. pub fn new() -> Self { Self::default() } // Handles the tick event of the terminal. pub fn tick(&self) {} // General commands // Set running to false to quit the application. pub fn quit(&mut self) { self.running = false; } // Toggle moveable list between entries and tags pub fn toggle_area(&mut self) { match self.current_area { CurrentArea::EntryArea => { self.current_area = CurrentArea::TagArea; self.tag_list.tag_list_state.select(Some(0)) } CurrentArea::TagArea => { self.current_area = CurrentArea::EntryArea; self.tag_list.tag_list_state.select(None) } CurrentArea::SearchArea => { if let Some(former_area) = &self.former_area { match former_area { FormerArea::EntryArea => self.current_area = CurrentArea::EntryArea, FormerArea::TagArea => self.current_area = CurrentArea::TagArea, _ => {} } } } CurrentArea::HelpArea => { if let Some(former_area) = &self.former_area { match former_area { FormerArea::EntryArea => self.current_area = CurrentArea::EntryArea, FormerArea::TagArea => self.current_area = CurrentArea::TagArea, FormerArea::SearchArea => self.current_area = CurrentArea::SearchArea, } } } } } // Yank the passed string to system clipboard pub fn yank_text(selection: &str) { let mut clipboard = Clipboard::new().unwrap(); let yanked_text = selection.to_string(); clipboard.set_text(yanked_text).unwrap(); } pub fn scroll_info_down(&mut self) { self.scroll_info = self.scroll_info + 1; } pub fn scroll_info_up(&mut self) { if self.scroll_info == 0 { {} } else { self.scroll_info = self.scroll_info - 1; } } pub fn select_none(&mut self) { match self.current_area { CurrentArea::EntryArea => self.entry_table.entry_table_state.select(None), CurrentArea::TagArea => self.tag_list.tag_list_state.select(None), _ => {} } // self.tag_list.tag_list_state.select(None); } // Tag List commands // Movement pub fn select_next_tag(&mut self) { self.tag_list.tag_list_state.select_next(); } pub fn select_previous_tag(&mut self) { self.tag_list.tag_list_state.select_previous(); } pub fn select_first_tag(&mut self) { self.tag_list.tag_list_state.select_first(); } pub fn select_last_tag(&mut self) { self.tag_list.tag_list_state.select_last(); } pub fn reset_taglist(&mut self) { self.tag_list = TagList::from_iter(self.main_biblio.citekeys.clone()) } // Entry Table commands // Movement pub fn select_next_entry(&mut self) { self.scroll_info = 0; self.entry_table.entry_table_state.select_next(); } pub fn select_previous_entry(&mut self) { self.scroll_info = 0; self.entry_table.entry_table_state.select_previous(); } pub fn select_first_entry(&mut self) { self.scroll_info = 0; self.entry_table.entry_table_state.select_first(); } pub fn select_last_entry(&mut self) { self.scroll_info = 0; self.entry_table.entry_table_state.select_last(); } // Get the citekey of the selected entry pub fn get_selected_citekey(&self) -> &str { let idx = self.entry_table.entry_table_state.selected().unwrap(); let citekey = &self.entry_table.entry_table_items[idx].citekey; citekey } // Reset Entry Table to initial state pub fn reset_entry_table(&mut self) { self.entry_table = EntryTable::from_iter(self.biblio_data.entry_list.bibentries.clone()) } // Search entry list pub fn search_entries(&mut self) { match self.former_area { Some(FormerArea::EntryArea) => { 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) } _ => {} } } }