diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.rs | 15 | ||||
| -rw-r--r-- | src/bibiman.rs | 28 | ||||
| -rw-r--r-- | src/bibiman/search.rs | 4 | ||||
| -rw-r--r-- | src/main.rs | 7 | ||||
| -rw-r--r-- | src/tui.rs | 3 | ||||
| -rw-r--r-- | src/tui/commands.rs | 4 | ||||
| -rw-r--r-- | src/tui/handler.rs | 197 | ||||
| -rw-r--r-- | src/tui/popup.rs | 104 | ||||
| -rw-r--r-- | src/tui/ui.rs | 36 |
9 files changed, 190 insertions, 208 deletions
@@ -19,6 +19,7 @@ use crate::bibiman::CurrentArea; // use super::Event; use crate::cliargs::CLIArgs; use crate::tui::commands::{InputCmdAction, OpenRessource}; +use crate::tui::popup::PopupKind; use crate::tui::{self, Tui}; use crate::{bibiman::Bibiman, tui::commands::CmdAction}; use color_eyre::eyre::{Ok, Result}; @@ -68,6 +69,11 @@ impl App { // Event::Key(key_event) => handle_key_events(key_event, self, &mut tui)?, // Event::Mouse(_) => {} Event::Key(key_event) => { + if let Some(PopupKind::Message) = self.bibiman.popup_area.popup_kind { + self.bibiman.popup_area.popup_close_message() + } else if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { + self.bibiman.go_back() + } let command = if self.input_mode { CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) } else { @@ -187,6 +193,8 @@ impl App { CmdAction::Confirm => { if let CurrentArea::TagArea = self.bibiman.current_area { self.bibiman.filter_for_tags(); + } else if let CurrentArea::PopupArea = self.bibiman.current_area { + self.bibiman.go_back(); } } CmdAction::SortList => { @@ -196,6 +204,10 @@ impl App { } CmdAction::YankItem => { Bibiman::yank_text(&self.bibiman.get_selected_citekey()); + self.bibiman.popup_area.popup_message( + "Yanked citekey to clipboard:", + self.bibiman.get_selected_citekey().to_string(), + ); } CmdAction::EditFile => { if let CurrentArea::EntryArea = self.bibiman.current_area { @@ -215,6 +227,9 @@ impl App { } OpenRessource::Note => {} }, + CmdAction::ShowHelp => { + self.bibiman.show_help(); + } CmdAction::Exit => { self.quit(); } diff --git a/src/bibiman.rs b/src/bibiman.rs index a573ee7..f3d9272 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -18,6 +18,7 @@ use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::{bibisetup::*, search::BibiSearch}; use crate::cliargs::CLIArgs; +use crate::tui::popup::{PopupArea, PopupKind}; use crate::tui::Tui; use crate::{bibiman::entries::EntryTable, bibiman::keywords::TagList}; use arboard::Clipboard; @@ -40,6 +41,7 @@ pub enum CurrentArea { EntryArea, TagArea, SearchArea, + PopupArea, } // Check which area was active when popup set active @@ -69,6 +71,8 @@ pub struct Bibiman { pub current_area: CurrentArea, // mode for popup window pub former_area: Option<FormerArea>, + // active popup + pub popup_area: PopupArea, } impl Bibiman { @@ -89,9 +93,33 @@ impl Bibiman { scroll_info: 0, current_area, former_area: None, + popup_area: PopupArea::default(), }) } + pub fn show_help(&mut self) { + if let CurrentArea::EntryArea = self.current_area { + self.former_area = Some(FormerArea::EntryArea); + } else if let CurrentArea::TagArea = self.current_area { + self.former_area = Some(FormerArea::TagArea); + } + self.popup_area.is_popup = true; + self.current_area = CurrentArea::PopupArea; + self.popup_area.popup_kind = Some(PopupKind::Help); + } + + pub fn go_back(&mut self) { + if let CurrentArea::PopupArea = self.current_area { + self.popup_area.is_popup = false; + self.popup_area.popup_kind = None; + if let Some(FormerArea::EntryArea) = self.former_area { + self.current_area = CurrentArea::EntryArea + } else if let Some(FormerArea::TagArea) = self.former_area { + self.current_area = CurrentArea::TagArea + } + }; + } + pub fn update_lists(&mut self) { self.main_biblio = BibiSetup::new(&self.main_bibfile); self.tag_list = TagList::new(self.main_biblio.keyword_list.clone()); diff --git a/src/bibiman/search.rs b/src/bibiman/search.rs index 362d0f9..896d1a4 100644 --- a/src/bibiman/search.rs +++ b/src/bibiman/search.rs @@ -139,8 +139,8 @@ mod tests { keywords: "hello, bye".to_string(), citekey: "author_1999".to_string(), abstract_text: "An abstract with multiple sentences. Here is the second".to_string(), - doi_url: "https://www.bibiman.org".to_string(), - filepath: "/home/file/path.pdf".to_string(), + doi_url: Some("https://www.bibiman.org".to_string()), + filepath: Some("/home/file/path.pdf".to_string()), subtitle: None, }; diff --git a/src/main.rs b/src/main.rs index c2c5121..8a78d62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,13 @@ pub mod cliargs; pub mod errorsetup; pub mod tui; +// Color indices +const MAIN_BLUE_COLOR_INDEX: u8 = 75; +const MAIN_PURPLE_COLOR_INDEX: u8 = 135; +const MAIN_GREEN_COLOR_INDEX: u8 = 29; +const TEXT_HIGHLIGHT_COLOR_INDEX: u8 = 254; +const TEXT_FG_COLOR_INDEX: u8 = 250; + #[tokio::main] async fn main() -> Result<()> { // Parse CLI arguments @@ -15,9 +15,8 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. ///// -// pub mod command; pub mod commands; -// pub mod handler; +pub mod popup; pub mod ui; use crate::App; diff --git a/src/tui/commands.rs b/src/tui/commands.rs index dc6b2c8..6a2ab13 100644 --- a/src/tui/commands.rs +++ b/src/tui/commands.rs @@ -67,6 +67,8 @@ pub enum CmdAction { Input(InputCmdAction), // Hexdump command. Exit, + // Show keybindings + ShowHelp, // Do nothing. Nothing, } @@ -148,6 +150,8 @@ impl From<KeyEvent> for CmdAction { KeyCode::Char('y') => Self::YankItem, // Sort entry table by selected col KeyCode::Char('s') => Self::SortList, + // Show help popup + KeyCode::Char('?') => Self::ShowHelp, // Else do nothing _ => Self::Nothing, } diff --git a/src/tui/handler.rs b/src/tui/handler.rs deleted file mode 100644 index ae2fd4d..0000000 --- a/src/tui/handler.rs +++ /dev/null @@ -1,197 +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 crate::bibiman::{Bibiman, CurrentArea}; -use crate::tui::Tui; -use crate::App; -use color_eyre::eyre::Result; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; - -/// Handles the key events and updates the state of [`App`]. -pub fn handle_key_events(key_event: KeyEvent, app: &mut App, tui: &mut Tui) -> Result<()> { - // Keycodes activated for every area (high priority) - match key_event.code { - // Exit application on `ESC` or `q` - KeyCode::Char('Q') | KeyCode::Char('q') => { - app.quit(); - } - // Exit application on `Ctrl-C` - KeyCode::Char('c') | KeyCode::Char('C') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.quit(); - } - } - KeyCode::PageDown => { - app.bibiman.scroll_info_down(); - } - KeyCode::PageUp => { - app.bibiman.scroll_info_up(); - } - _ => {} - } - // Keycodes for specific areas - match app.bibiman.current_area { - // Keycodes for the tag area - CurrentArea::TagArea => match key_event.code { - KeyCode::Down => { - app.bibiman.select_next_tag(1); - } - KeyCode::Up => { - app.bibiman.select_previous_tag(1); - } - KeyCode::Char('j') => { - if key_event.modifiers == KeyModifiers::ALT { - app.bibiman.scroll_info_down(); - } else { - app.bibiman.select_next_tag(1); - } - } - KeyCode::Char('k') => { - if key_event.modifiers == KeyModifiers::ALT { - app.bibiman.scroll_info_up(); - } else { - app.bibiman.select_previous_tag(1); - } - } - KeyCode::Char('d') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.bibiman.select_next_tag(5) - } - } - KeyCode::Char('u') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.bibiman.select_previous_tag(5) - } - } - KeyCode::Char('g') | KeyCode::Home => { - app.bibiman.select_first_tag(); - } - KeyCode::Char('G') | KeyCode::End => { - app.bibiman.select_last_tag(); - } - KeyCode::Char('/') => { - app.bibiman.enter_search_area(); - } - KeyCode::Char('f') | KeyCode::Char('F') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.bibiman.enter_search_area(); - } - } - KeyCode::Tab | KeyCode::BackTab => { - app.bibiman.toggle_area(); - } - KeyCode::Esc => { - app.bibiman.reset_current_list(); - } - KeyCode::Enter => { - app.bibiman.filter_for_tags(); - } - _ => {} - }, - // Keycodes for the entry area - CurrentArea::EntryArea => match key_event.code { - KeyCode::Down => { - app.bibiman.select_next_entry(1); - } - KeyCode::Up => { - app.bibiman.select_previous_entry(1); - } - KeyCode::Char('j') => { - if key_event.modifiers == KeyModifiers::ALT { - app.bibiman.scroll_info_down(); - } else { - app.bibiman.select_next_entry(1); - } - } - KeyCode::Char('k') => { - if key_event.modifiers == KeyModifiers::ALT { - app.bibiman.scroll_info_up(); - } else { - app.bibiman.select_previous_entry(1); - } - } - KeyCode::Char('d') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.bibiman.select_next_entry(5); - } - } - KeyCode::Char('u') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.bibiman.select_previous_entry(5); - } else { - app.bibiman.open_doi_url()?; - } - } - KeyCode::Char('g') | KeyCode::Home => { - app.bibiman.select_first_entry(); - } - KeyCode::Char('G') | KeyCode::End => { - app.bibiman.select_last_entry(); - } - KeyCode::Char('h') => { - app.bibiman.select_prev_column(); - } - KeyCode::Char('l') => { - app.bibiman.select_next_column(); - } - KeyCode::Char('s') => { - app.bibiman.entry_table.sort_entry_table(true); - } - KeyCode::Char('y') => { - Bibiman::yank_text(&app.bibiman.get_selected_citekey()); - } - KeyCode::Char('e') => { - app.bibiman.run_editor(tui)?; - } - KeyCode::Char('o') => { - app.bibiman.open_connected_file()?; - } - KeyCode::Char('/') => { - app.bibiman.enter_search_area(); - } - KeyCode::Char('f') | KeyCode::Char('F') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.bibiman.enter_search_area(); - } - } - KeyCode::Tab | KeyCode::BackTab => { - app.bibiman.toggle_area(); - } - KeyCode::Esc => { - app.bibiman.reset_current_list(); - } - _ => {} - }, - // Keycodes for the search area (rendered in footer) - CurrentArea::SearchArea => match key_event.code { - KeyCode::Esc => { - app.bibiman.break_search(); - } - KeyCode::Enter => { - app.bibiman.confirm_search(); - } - KeyCode::Backspace => { - app.bibiman.search_pattern_pop(); - } - KeyCode::Char(search_pattern) => { - app.bibiman.search_pattern_push(search_pattern); - } - _ => {} - }, - } - Ok(()) -} diff --git a/src/tui/popup.rs b/src/tui/popup.rs new file mode 100644 index 0000000..60c58b4 --- /dev/null +++ b/src/tui/popup.rs @@ -0,0 +1,104 @@ +// 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::{ + style::{Color, Stylize}, + text::{Line, Span, Text}, + widgets::ListState, +}; + +use crate::MAIN_PURPLE_COLOR_INDEX; + +#[derive(Debug)] +pub enum PopupKind { + Help, + Message, + Selection, +} + +#[derive(Debug)] +pub struct PopupArea { + pub is_popup: bool, + pub popup_kind: Option<PopupKind>, + pub popup_message: String, + pub popup_list: Vec<String>, + pub popup_state: ListState, +} + +impl Default for PopupArea { + fn default() -> Self { + PopupArea { + is_popup: false, + popup_kind: None, + popup_message: String::new(), + popup_list: Vec::new(), + popup_state: ListState::default(), + } + } +} + +impl PopupArea { + pub fn popup_help<'a>() -> Text<'a> { + let help = [ + ("j,k|↓,↑: ", "Select next/previous item"), + ("h,l|←,→: ", "Select next/previous column (Entry table)"), + ("g|Home: ", "Go to first item"), + ("G|End: ", "Go to last item"), + ("s: ", "sort entries by selected column (toggles reversed)"), + ("TAB: ", "Toggle areas (Entries, Keywords)"), + ("/|Ctrl+f: ", "Enter search mode"), + ("y: ", "yank/copy citekey of selected entry to clipboard"), + ("e: ", "Open editor at selected entry"), + ("o: ", "Open with selected entry associated PDF"), + ("u: ", "Open DOI/URL of selected entry"), + ("ESC: ", "Reset all lists/abort search"), + ("ENTER: ", "Confirm search/filter by selected keyword"), + ("q|Ctrl+c: ", "Quit bibiman"), + ]; + + let help_text: Vec<Line<'_>> = help + .into_iter() + .map(|(keys, help)| { + Line::from(vec![ + Span::raw(keys) + .bold() + .fg(Color::Indexed(MAIN_PURPLE_COLOR_INDEX)), + Span::raw(help), + ]) + }) + .collect(); + + let text = Text::from(help_text); + text + } + + pub fn popup_message(&mut self, message: &str, object: String) { + if object.is_empty() { + self.popup_message = message.into(); + } else { + self.popup_message = format!("{} \"{}\"", message, object); + } + self.popup_kind = Some(PopupKind::Message); + self.is_popup = true; + } + + pub fn popup_close_message(&mut self) { + self.is_popup = false; + self.popup_message.clear(); + self.popup_kind = None + } +} diff --git a/src/tui/ui.rs b/src/tui/ui.rs index f2091a5..260f401 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -15,9 +15,15 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. ///// +use super::popup::PopupArea; use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::{CurrentArea, FormerArea}; +use crate::tui::popup::PopupKind; use crate::App; +use crate::{ + MAIN_BLUE_COLOR_INDEX, MAIN_GREEN_COLOR_INDEX, MAIN_PURPLE_COLOR_INDEX, TEXT_FG_COLOR_INDEX, + TEXT_HIGHLIGHT_COLOR_INDEX, +}; use ratatui::layout::{Direction, Position}; use ratatui::widgets::Clear; use ratatui::Frame; @@ -31,13 +37,7 @@ use ratatui::{ ScrollbarOrientation, Table, Wrap, }, }; - -// Color indices -const MAIN_BLUE_COLOR_INDEX: u8 = 75; -const MAIN_PURPLE_COLOR_INDEX: u8 = 135; -const MAIN_GREEN_COLOR_INDEX: u8 = 29; -const TEXT_HIGHLIGHT_COLOR_INDEX: u8 = 254; -const TEXT_FG_COLOR_INDEX: u8 = 250; +use tui_popup::Popup; // Text colors const TEXT_FG_COLOR: Color = Color::Indexed(TEXT_FG_COLOR_INDEX); @@ -48,6 +48,7 @@ const MAIN_GREEN: Color = Color::Indexed(MAIN_GREEN_COLOR_INDEX); // Background colors const HEADER_FOOTER_BG: Color = Color::Indexed(235); +const POPUP_BG: Color = Color::Indexed(233); // Box styles // Keyword Box @@ -72,6 +73,8 @@ const BOX_SELECTED_TITLE_STYLE: Style = Style::new() const BOX_UNSELECTED_BORDER_STYLE: Style = Style::new().fg(TEXT_FG_COLOR); const BOX_UNSELECTED_TITLE_STYLE: Style = Style::new().fg(TEXT_FG_COLOR).add_modifier(Modifier::BOLD); +// Popup box +const POPUP_HELP_BOX: Style = Style::new().fg(TEXT_FG_COLOR).bg(POPUP_BG); // Entry table styles const ENTRY_SELECTED_ROW_STYLE: Style = Style::new() @@ -142,6 +145,25 @@ pub fn render_ui(app: &mut App, frame: &mut Frame) { render_selected_item(app, frame, info_area); render_taglist(app, frame, tag_area); render_file_info(app, frame, entry_info_area); + if app.bibiman.popup_area.is_popup { + render_popup(app, frame); + } +} + +pub fn render_popup(app: &mut App, frame: &mut Frame) { + let popup = if let Some(PopupKind::Help) = app.bibiman.popup_area.popup_kind { + Popup::new(PopupArea::popup_help()) + .title(" Keybindings ".bold().into_centered_line()) + .style(POPUP_HELP_BOX) + .border_style(Style::new().fg(MAIN_BLUE)) + } else if let Some(PopupKind::Message) = app.bibiman.popup_area.popup_kind { + Popup::new(Text::from(app.bibiman.popup_area.popup_message.as_str()).fg(MAIN_GREEN)) + .title(" Message ".bold().into_centered_line().fg(MAIN_GREEN)) + .style(POPUP_HELP_BOX) + } else { + panic!("No popup text detected") + }; + frame.render_widget(&popup, frame.area()) } pub fn render_header(frame: &mut Frame, rect: Rect) { |
