diff options
| author | lukeflo | 2024-10-24 12:53:09 +0200 |
|---|---|---|
| committer | lukeflo | 2024-10-24 12:53:09 +0200 |
| commit | f32b6a19851b8b103ac843503ab008197f0639cd (patch) | |
| tree | 49216019cf71b4c3161eb5b1a74d9ee7e06eceb0 /src/tui | |
| parent | 66402a9c23e0975a8a3d8c2707b689b9cde98ccf (diff) | |
| download | bibiman-f32b6a19851b8b103ac843503ab008197f0639cd.tar.gz bibiman-f32b6a19851b8b103ac843503ab008197f0639cd.zip | |
rearrange code again, prepare for command-action setup
Diffstat (limited to 'src/tui')
| -rw-r--r-- | src/tui/app.rs | 257 | ||||
| -rw-r--r-- | src/tui/command.rs | 10 | ||||
| -rw-r--r-- | src/tui/commandnew.rs | 178 | ||||
| -rw-r--r-- | src/tui/handler.rs | 97 | ||||
| -rw-r--r-- | src/tui/ui.rs | 22 |
5 files changed, 242 insertions, 322 deletions
diff --git a/src/tui/app.rs b/src/tui/app.rs deleted file mode 100644 index b09ae80..0000000 --- a/src/tui/app.rs +++ /dev/null @@ -1,257 +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::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; - -// Areas in which actions are possible -#[derive(Debug)] -pub enum CurrentArea { - EntryArea, - TagArea, - SearchArea, - HelpArea, - InfoArea, -} - -// 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 bib file - pub main_bibfile: PathBuf, - // main bibliography - pub main_biblio: BibiMain, - // search struct: - pub search_struct: BibiSearch, - // 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<FormerArea>, -} - -impl App { - // Constructs a new instance of [`App`]. - pub fn new(args: CLIArgs) -> Result<Self> { - // Self::default() - let running = true; - let main_bibfile = args.bibfilearg; - let main_biblio = BibiMain::new(main_bibfile.clone()); - let tag_list = TagList::new(main_biblio.keyword_list.clone()); - let search_struct = BibiSearch::default(); - let entry_table = EntryTable::new(main_biblio.entry_list.clone()); - let current_area = CurrentArea::EntryArea; - Ok(Self { - running, - main_bibfile, - main_biblio, - tag_list, - search_struct, - entry_table, - scroll_info: 0, - current_area, - former_area: None, - }) - } - - pub async fn run(&mut self) -> Result<()> { - let mut tui = tui::Tui::new()?; - tui.enter()?; - - // Start the main loop. - while self.running { - // Render the user interface. - tui.draw(self)?; - // Handle events. - match tui.next().await? { - Event::Tick => self.tick(), - Event::Key(key_event) => handle_key_events(key_event, self, &mut tui)?, - Event::Mouse(_) => {} - Event::Resize(_, _) => {} - } - } - - // Exit the user interface. - tui.exit()?; - Ok(()) - } - - // 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; - } - - pub fn update_lists(&mut self) { - self.main_biblio = BibiMain::new(self.main_bibfile.clone()); - // 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::new(self.main_biblio.entry_list.clone()); - } - - // Toggle moveable list between entries and tags - pub fn toggle_area(&mut self) { - if let CurrentArea::EntryArea = self.current_area { - self.entry_table.entry_scroll_state = self.entry_table.entry_scroll_state.position(0); - self.current_area = CurrentArea::TagArea; - self.tag_list.tag_list_state.select(Some(0)); - self.tag_list.tag_scroll_state = self - .tag_list - .tag_scroll_state - .position(self.tag_list.tag_list_state.selected().unwrap()); - } else if let CurrentArea::TagArea = self.current_area { - self.current_area = CurrentArea::EntryArea; - self.tag_list.tag_list_state.select(None); - self.entry_table.entry_scroll_state = self - .entry_table - .entry_scroll_state - .position(self.entry_table.entry_table_state.selected().unwrap()); - } - } - - pub fn reset_current_list(&mut self) { - 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)) - } - self.entry_table.entry_table_at_search_start.clear(); - self.search_struct.filtered_tag_list.clear(); - self.search_struct.inner_search = false; - self.former_area = None - } - - // 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.entry_table.entry_info_scroll = self.entry_table.entry_info_scroll.saturating_add(1); - self.entry_table.entry_info_scroll_state = self - .entry_table - .entry_info_scroll_state - .position(self.entry_table.entry_info_scroll.into()); - } - - pub fn scroll_info_up(&mut self) { - self.entry_table.entry_info_scroll = self.entry_table.entry_info_scroll.saturating_sub(1); - self.entry_table.entry_info_scroll_state = self - .entry_table - .entry_info_scroll_state - .position(self.entry_table.entry_info_scroll.into()); - } - - // Search Area - - // Enter the search area - pub fn enter_search_area(&mut self) { - if let CurrentArea::EntryArea = self.current_area { - if let Some(FormerArea::TagArea) = self.former_area { - self.search_struct.inner_search = true - } - self.entry_table.entry_table_at_search_start = - self.entry_table.entry_table_items.clone(); - self.former_area = Some(FormerArea::EntryArea) - } else if let CurrentArea::TagArea = self.current_area { - self.former_area = Some(FormerArea::TagArea) - } - self.current_area = CurrentArea::SearchArea - } - - // Confirm search: Search former list by pattern - pub fn confirm_search(&mut self) { - if let Some(FormerArea::EntryArea) = self.former_area { - self.current_area = CurrentArea::EntryArea; - self.entry_table.entry_table_state.select(Some(0)) - } else if let Some(FormerArea::TagArea) = self.former_area { - self.current_area = CurrentArea::TagArea; - self.tag_list.tag_list_state.select(Some(0)) - } - self.former_area = Some(FormerArea::SearchArea); - self.search_struct.search_string.clear(); - self.entry_table.entry_table_at_search_start.clear(); - } - - // Break search: leave search area without filtering list - pub fn break_search(&mut self) { - if let Some(FormerArea::EntryArea) = self.former_area { - self.current_area = CurrentArea::EntryArea; - self.entry_table.entry_table_state.select(Some(0)) - } else if let Some(FormerArea::TagArea) = self.former_area { - self.current_area = CurrentArea::TagArea; - self.tag_list.tag_list_state.select(Some(0)) - } - // But keep filtering by tag if applied before entering search area - if !self.search_struct.inner_search { - self.reset_current_list(); - } - self.former_area = None; - // If search is canceled, reset default status of struct - self.search_struct.search_string.clear(); - self.entry_table.entry_table_at_search_start.clear(); - } - - // Remove last char from search pattern and filter list immidiately - pub fn search_pattern_pop(&mut self) { - self.search_struct.search_string.pop(); - if let Some(FormerArea::EntryArea) = self.former_area { - self.search_entries(); - self.filter_tags_by_entries(); - } else if let Some(FormerArea::TagArea) = self.former_area { - self.search_tags(); - } - } - - // Add current char to search pattern and filter list immidiatley - pub fn search_pattern_push(&mut self, search_pattern: char) { - self.search_struct.search_string.push(search_pattern); - if let Some(FormerArea::EntryArea) = self.former_area { - self.search_entries(); - self.filter_tags_by_entries(); - } else if let Some(FormerArea::TagArea) = self.former_area { - self.search_tags(); - } - } -} diff --git a/src/tui/command.rs b/src/tui/command.rs index 9f25f5f..8416a3e 100644 --- a/src/tui/command.rs +++ b/src/tui/command.rs @@ -15,9 +15,9 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. ///// -use crate::bib::entries::EntryTableColumn; -use crate::bib::search::BibiSearch; -use crate::tui::app::{App, FormerArea}; +use crate::bibiman::entries::EntryTableColumn; +use crate::bibiman::search::BibiSearch; +use crate::bibiman::{Bibiman, FormerArea}; use crate::tui::Tui; use color_eyre::eyre::{Context, Ok, Result}; use core::panic; @@ -25,7 +25,7 @@ use editor_command::EditorBuilder; use ratatui::widgets::ScrollbarState; use std::process::{Command, Stdio}; -impl App { +impl Bibiman { // Entry Table commands // Movement @@ -260,7 +260,7 @@ impl App { } } -impl App { +impl Bibiman { // Tag List commands // Movement diff --git a/src/tui/commandnew.rs b/src/tui/commandnew.rs new file mode 100644 index 0000000..45b2f52 --- /dev/null +++ b/src/tui/commandnew.rs @@ -0,0 +1,178 @@ +// 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::crossterm::event::{ + Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, +}; +use tui_input::Input; + +// Possible scroll areas. +#[derive(Debug, PartialEq, Eq)] +pub enum ScrollType { + Rows, + Cols, + InfoArea, +} + +// Possible ressources to open +#[derive(Debug, PartialEq, Eq)] +pub enum OpenRessource { + PDF, + WebLink, + Note, +} + +/// Application command. +#[derive(Debug, PartialEq, Eq)] +pub enum Command { + // Toggle area + ToggleArea, + // Next + Next(ScrollType, usize), + // Previous. + Previous(ScrollType, usize), + // Go to top. + Top, + // Go to bottom. + Bottom, + // Search list + SearchList, + // Reset lists + ResetList, + // Confirm search/selection + Confirm, + // Sort table/list + SortList, + // Yank selected item + YankItem, + // Edit file + EditFile, + // Open linked ressource + Open(OpenRessource), + // Input command. + Input(InputCommand), + // Hexdump command. + Exit, + // Do nothing. + Nothing, +} + +impl From<KeyEvent> for Command { + fn from(key_event: KeyEvent) -> Self { + match key_event.code { + // Go to first/last entry of selected list/table + KeyCode::Char('g') | KeyCode::Home => Self::Top, + KeyCode::Char('G') | KeyCode::End => Self::Bottom, + // Scroll columns of EntryTable + KeyCode::Right | KeyCode::Char('l') => Self::Next(ScrollType::Cols, 1), + KeyCode::Left | KeyCode::Char('h') => Self::Previous(ScrollType::Cols, 1), + // Scroll table/list vertically by 1 + KeyCode::Down | KeyCode::Char('j') => Self::Next(ScrollType::Rows, 1), + KeyCode::Up | KeyCode::Char('k') => Self::Previous(ScrollType::Rows, 1), + // Scroll table/list vertically by 5 + KeyCode::PageDown => Self::Next(ScrollType::Rows, 5), + KeyCode::PageUp => Self::Previous(ScrollType::Rows, 5), + KeyCode::Char('d') => { + if key_event.modifiers == KeyModifiers::CONTROL { + Self::Next(ScrollType::Rows, 5) + } else { + Self::Nothing + } + } + KeyCode::Char('u') => { + if key_event.modifiers == KeyModifiers::CONTROL { + Self::Previous(ScrollType::Rows, 5) + } else { + Self::Open(OpenRessource::WebLink) + } + } + // Exit App + KeyCode::Char('q') => Self::Exit, + KeyCode::Char('c') | KeyCode::Char('C') => { + if key_event.modifiers == KeyModifiers::CONTROL { + Self::Exit + } else { + Self::Nothing + } + } + // Switch selected area + KeyCode::Tab => Self::ToggleArea, + KeyCode::BackTab => Self::ToggleArea, + // Enter search mode + KeyCode::Char('/') => Self::Input(InputCommand::Enter), + KeyCode::Char('f') => { + if key_event.modifiers == KeyModifiers::CONTROL { + Self::Input(InputCommand::Enter) + } else { + Self::Nothing + } + } + // KeyCode::Backspace => Self::Input(InputCommand::Resume(Event::Key(key_event))), + // Confirm selection + KeyCode::Enter => Self::Confirm, + // Reset lists/tables + KeyCode::Esc => Self::ResetList, + // Open linked ressource + KeyCode::Char('o') => Self::Open(OpenRessource::PDF), + // KeyCode::Char('u') => Self::Open(OpenRessource::WebLink), + // Edit currently selected entry + KeyCode::Char('e') => Self::EditFile, + // Yank selected item/value + KeyCode::Char('y') => Self::YankItem, + // Else do nothing + _ => Self::Nothing, + } + } +} + +impl From<MouseEvent> for Command { + fn from(mouse_event: MouseEvent) -> Self { + match mouse_event.kind { + MouseEventKind::ScrollDown => Self::Next(ScrollType::Rows, 1), + MouseEventKind::ScrollUp => Self::Previous(ScrollType::Rows, 1), + _ => Self::Nothing, + } + } +} + +/// Input mode command. +#[derive(Debug, PartialEq, Eq)] +pub enum InputCommand { + // Handle input. + Handle(Event), + // Enter input mode. + Enter, + // Confirm input. + Confirm, + // Exit input mode + Exit, +} + +impl InputCommand { + /// Parses the event. + pub fn parse(key_event: KeyEvent, input: &Input) -> Self { + if key_event.code == KeyCode::Esc + || (key_event.code == KeyCode::Backspace && input.value().is_empty()) + { + Self::Exit + } else if key_event.code == KeyCode::Enter { + Self::Confirm + } else { + Self::Handle(Event::Key(key_event)) + } + } +} diff --git a/src/tui/handler.rs b/src/tui/handler.rs index 5a196b5..3a4d055 100644 --- a/src/tui/handler.rs +++ b/src/tui/handler.rs @@ -15,12 +15,11 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. ///// -use crate::tui::app::App; +use crate::bibiman::{Bibiman, CurrentArea}; use crate::tui::Tui; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; - -use super::app::CurrentArea; +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<()> { @@ -37,159 +36,159 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App, tui: &mut Tui) -> R } } KeyCode::PageDown => { - app.scroll_info_down(); + app.bibiman.scroll_info_down(); } KeyCode::PageUp => { - app.scroll_info_up(); + app.bibiman.scroll_info_up(); } _ => {} } // Keycodes for specific areas - match app.current_area { + match app.bibiman.current_area { // Keycodes for the tag area CurrentArea::TagArea => match key_event.code { KeyCode::Down => { - app.select_next_tag(1); + app.bibiman.select_next_tag(1); } KeyCode::Up => { - app.select_previous_tag(1); + app.bibiman.select_previous_tag(1); } KeyCode::Char('j') => { if key_event.modifiers == KeyModifiers::ALT { - app.scroll_info_down(); + app.bibiman.scroll_info_down(); } else { - app.select_next_tag(1); + app.bibiman.select_next_tag(1); } } KeyCode::Char('k') => { if key_event.modifiers == KeyModifiers::ALT { - app.scroll_info_up(); + app.bibiman.scroll_info_up(); } else { - app.select_previous_tag(1); + app.bibiman.select_previous_tag(1); } } KeyCode::Char('d') => { if key_event.modifiers == KeyModifiers::CONTROL { - app.select_next_tag(5) + app.bibiman.select_next_tag(5) } } KeyCode::Char('u') => { if key_event.modifiers == KeyModifiers::CONTROL { - app.select_previous_tag(5) + app.bibiman.select_previous_tag(5) } } KeyCode::Char('g') | KeyCode::Home => { - app.select_first_tag(); + app.bibiman.select_first_tag(); } KeyCode::Char('G') | KeyCode::End => { - app.select_last_tag(); + app.bibiman.select_last_tag(); } KeyCode::Char('/') => { - app.enter_search_area(); + app.bibiman.enter_search_area(); } KeyCode::Char('f') | KeyCode::Char('F') => { if key_event.modifiers == KeyModifiers::CONTROL { - app.enter_search_area(); + app.bibiman.enter_search_area(); } } KeyCode::Tab | KeyCode::BackTab => { - app.toggle_area(); + app.bibiman.toggle_area(); } KeyCode::Esc => { - app.reset_current_list(); + app.bibiman.reset_current_list(); } KeyCode::Enter => { - app.filter_for_tags(); + app.bibiman.filter_for_tags(); } _ => {} }, // Keycodes for the entry area CurrentArea::EntryArea => match key_event.code { KeyCode::Down => { - app.select_next_entry(1); + app.bibiman.select_next_entry(1); } KeyCode::Up => { - app.select_previous_entry(1); + app.bibiman.select_previous_entry(1); } KeyCode::Char('j') => { if key_event.modifiers == KeyModifiers::ALT { - app.scroll_info_down(); + app.bibiman.scroll_info_down(); } else { - app.select_next_entry(1); + app.bibiman.select_next_entry(1); } } KeyCode::Char('k') => { if key_event.modifiers == KeyModifiers::ALT { - app.scroll_info_up(); + app.bibiman.scroll_info_up(); } else { - app.select_previous_entry(1); + app.bibiman.select_previous_entry(1); } } KeyCode::Char('d') => { if key_event.modifiers == KeyModifiers::CONTROL { - app.select_next_entry(5); + app.bibiman.select_next_entry(5); } } KeyCode::Char('u') => { if key_event.modifiers == KeyModifiers::CONTROL { - app.select_previous_entry(5); + app.bibiman.select_previous_entry(5); } else { - app.open_doi_url()?; + app.bibiman.open_doi_url()?; } } KeyCode::Char('g') | KeyCode::Home => { - app.select_first_entry(); + app.bibiman.select_first_entry(); } KeyCode::Char('G') | KeyCode::End => { - app.select_last_entry(); + app.bibiman.select_last_entry(); } KeyCode::Char('h') => { - app.select_prev_column(); + app.bibiman.select_prev_column(); } KeyCode::Char('l') => { - app.select_next_column(); + app.bibiman.select_next_column(); } KeyCode::Char('s') => { - app.entry_table.sort_entry_table(true); + app.bibiman.entry_table.sort_entry_table(true); } KeyCode::Char('y') => { - App::yank_text(&app.get_selected_citekey()); + Bibiman::yank_text(&app.bibiman.get_selected_citekey()); } KeyCode::Char('e') => { - app.run_editor(tui)?; + app.bibiman.run_editor(tui)?; } KeyCode::Char('o') => { - app.open_connected_file()?; + app.bibiman.open_connected_file()?; } KeyCode::Char('/') => { - app.enter_search_area(); + app.bibiman.enter_search_area(); } KeyCode::Char('f') | KeyCode::Char('F') => { if key_event.modifiers == KeyModifiers::CONTROL { - app.enter_search_area(); + app.bibiman.enter_search_area(); } } KeyCode::Tab | KeyCode::BackTab => { - app.toggle_area(); + app.bibiman.toggle_area(); } KeyCode::Esc => { - app.reset_current_list(); + app.bibiman.reset_current_list(); } _ => {} }, // Keycodes for the search area (rendered in footer) CurrentArea::SearchArea => match key_event.code { KeyCode::Esc => { - app.break_search(); + app.bibiman.break_search(); } KeyCode::Enter => { - app.confirm_search(); + app.bibiman.confirm_search(); } KeyCode::Backspace => { - app.search_pattern_pop(); + app.bibiman.search_pattern_pop(); } KeyCode::Char(search_pattern) => { - app.search_pattern_push(search_pattern); + app.bibiman.search_pattern_push(search_pattern); } _ => {} }, @@ -199,8 +198,8 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App, tui: &mut Tui) -> R app.quit(); } KeyCode::Esc => { - app.toggle_area(); - app.former_area = None; + app.bibiman.toggle_area(); + app.bibiman.former_area = None; } _ => {} }, diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 07bc88d..d5571c8 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -15,10 +15,10 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. ///// -use super::app::{CurrentArea, FormerArea}; -use crate::bib::entries::EntryTableColumn; -use crate::bib::keywords::TagListItem; -use crate::tui::app::App; +use crate::bibiman::entries::EntryTableColumn; +use crate::bibiman::keywords::TagListItem; +use crate::bibiman::{Bibiman, CurrentArea, FormerArea}; +use crate::App; use ratatui::{ buffer::Buffer, layout::{Alignment, Constraint, Layout, Rect}, @@ -87,18 +87,18 @@ impl Widget for &mut App { Layout::horizontal([Constraint::Max(25), Constraint::Min(35)]).areas(item_area); // Render header and footer - App::render_header(header_area, buf); - self.render_footer(footer_area, buf); + Bibiman::render_header(header_area, buf); + self.bibiman.render_footer(footer_area, buf); // Render list area where entry gets selected - self.render_entrytable(entry_area, buf); - self.render_file_info(entry_info_area, buf); + self.bibiman.render_entrytable(entry_area, buf); + self.bibiman.render_file_info(entry_info_area, buf); // Render infos related to selected entry - self.render_taglist(tag_area, buf); - self.render_selected_item(info_area, buf); + self.bibiman.render_taglist(tag_area, buf); + self.bibiman.render_selected_item(info_area, buf); } } -impl App { +impl Bibiman { pub fn render_header(area: Rect, buf: &mut Buffer) { Paragraph::new("BIBIMAN – BibLaTeX manager TUI") .bold() |
