From 86d48aa48b9951b6cbc471113844d16683051f8f Mon Sep 17 00:00:00 2001 From: Trim Bresilla Date: Tue, 3 Dec 2024 01:45:29 +0100 Subject: feat: implement entry management in Bibiman TUI - Add `.devbox` to the `.gitignore` file - Create a new backup file `devbox.json.back` with package and shell initialization configurations - Introduce a new method `add_entry` in the Bibiman struct to manage adding entries - Implement functionality to handle new entry submissions using `doi2bib` - Update command actions to include `AddEntry` - Add `AddEntry` as a new popup type in the TUI for creating entries - Enhance the UI rendering to support the new entry popup with input fields and cursor positioning --- src/app.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 6 deletions(-) (limited to 'src/app.rs') diff --git a/src/app.rs b/src/app.rs index 54d42b6..ae45355 100644 --- a/src/app.rs +++ b/src/app.rs @@ -23,6 +23,7 @@ use crate::tui::commands::InputCmdAction; use crate::tui::popup::PopupKind; use crate::tui::{self, Tui}; use crate::{bibiman::Bibiman, tui::commands::CmdAction}; +use ratatui::crossterm::event::KeyCode; use std::ffi::OsStr; use std::path::PathBuf; use std::process::{Command, Stdio}; @@ -78,14 +79,71 @@ impl App { } else if let Some(PopupKind::MessageError) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup() - } - let command = if self.input_mode { - CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) + } else if let Some(PopupKind::AddEntry) = self.bibiman.popup_area.popup_kind { + // Handle key events for AddEntry popup + match key_event.code { + KeyCode::Char(c) => { + let index = self.bibiman.popup_area.add_entry_cursor_position; + self.bibiman.popup_area.add_entry_input.insert(index, c); + self.bibiman.popup_area.add_entry_cursor_position += 1; + } + KeyCode::Backspace => { + if self.bibiman.popup_area.add_entry_cursor_position > 0 { + self.bibiman.popup_area.add_entry_cursor_position -= 1; + let index = self.bibiman.popup_area.add_entry_cursor_position; + self.bibiman.popup_area.add_entry_input.remove(index); + } + } + KeyCode::Left => { + if self.bibiman.popup_area.add_entry_cursor_position > 0 { + self.bibiman.popup_area.add_entry_cursor_position -= 1; + } + } + KeyCode::Right => { + if self.bibiman.popup_area.add_entry_cursor_position + < self.bibiman.popup_area.add_entry_input.len() + { + self.bibiman.popup_area.add_entry_cursor_position += 1; + } + } + KeyCode::Enter => { + // Handle submission of the new entry + self.bibiman.handle_new_entry_submission(); + self.bibiman.close_popup(); + self.input_mode = false; + } + KeyCode::Esc => { + // Close the popup without saving + self.bibiman.close_popup(); + self.input_mode = false; + } + _ => {} + } } else { - CmdAction::from(key_event) - }; - self.run_command(command, args, &mut tui)? + let command = if self.input_mode { + CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) + } else { + CmdAction::from(key_event) + }; + self.run_command(command, args, &mut tui)? + } } + // Event::Key(key_event) => { + // // Automatically close message popups on next keypress + // if let Some(PopupKind::MessageConfirm) = self.bibiman.popup_area.popup_kind { + // self.bibiman.close_popup() + // } else if let Some(PopupKind::MessageError) = self.bibiman.popup_area.popup_kind + // { + // self.bibiman.close_popup() + // } + + // let command = if self.input_mode { + // CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) + // } else { + // CmdAction::from(key_event) + // }; + // self.run_command(command, args, &mut tui)? + // } Event::Mouse(mouse_event) => { self.run_command(CmdAction::from(mouse_event), args, &mut tui)? } @@ -312,6 +370,9 @@ impl App { } } } + CmdAction::AddEntry => { + self.bibiman.add_entry(); + } CmdAction::ShowHelp => { self.bibiman.show_help(); } -- cgit v1.2.3 From 98deb23810ba4142082f82939f25d44d3094a34f Mon Sep 17 00:00:00 2001 From: Trim Bresilla Date: Tue, 3 Dec 2024 02:23:09 +0100 Subject: feat: finalize basic "AddEntry" to add from DOI - Modify the `handle_new_entry_submission` method to accept additional arguments. - Add error handling for failed inserts when appending to the file. - Introduce a new method `append_to_file` to handle file appending logic. - Update file handling to ensure new entries are correctly written to the specified file. --- src/app.rs | 2 +- src/bibiman.rs | 28 +++++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) (limited to 'src/app.rs') diff --git a/src/app.rs b/src/app.rs index ae45355..a200448 100644 --- a/src/app.rs +++ b/src/app.rs @@ -108,7 +108,7 @@ impl App { } KeyCode::Enter => { // Handle submission of the new entry - self.bibiman.handle_new_entry_submission(); + self.bibiman.handle_new_entry_submission(args); self.bibiman.close_popup(); self.input_mode = false; } diff --git a/src/bibiman.rs b/src/bibiman.rs index ee52207..e176bfa 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -28,6 +28,8 @@ use editor_command::EditorBuilder; use futures::executor::block_on; use ratatui::widgets::ScrollbarState; use std::fs; +use std::fs::OpenOptions; +use std::io::Write; use std::process::Command; use std::result::Result::Ok as AOk; use tui_input::Input; @@ -121,20 +123,25 @@ impl Bibiman { self.popup_area.popup_kind = Some(PopupKind::AddEntry); } - pub fn handle_new_entry_submission(&mut self) { + pub fn handle_new_entry_submission(&mut self, args: &CLIArgs) { let new_entry_title = self.popup_area.add_entry_input.trim(); let doi2bib = doi2bib::Doi2Bib::new().unwrap(); let new_entry_future = doi2bib.resolve_doi(new_entry_title); let new_entry = block_on(new_entry_future); if let AOk(entry) = new_entry { - println!("New entry: {:?}", entry); - // Add logic here to integrate the new entry into your application + // TODO: Add error handling for failed insert + if self.append_to_file(args, &entry.to_string()).is_err() { + self.popup_area.popup_kind = Some(PopupKind::MessageError); + self.popup_area.popup_message = "Failed to add new entry".to_string(); + } + // TODO: Add error handling for failed DOI lookup } else { self.popup_area.popup_kind = Some(PopupKind::MessageError); self.popup_area.popup_message = "Failed to add new entry".to_string(); } } + pub fn close_popup(&mut self) { // Reset all popup fields to default values self.popup_area = PopupArea::default(); @@ -414,6 +421,21 @@ impl Bibiman { Ok(()) } + /// Appends a string to the appropriate BibLaTeX file. + pub fn append_to_file(&mut self, args: &CLIArgs, content: &str) -> Result<()> { + // Determine the file path to append to + let file_path = args.files.first().unwrap(); + // Open the file in append mode + let mut file = OpenOptions::new().append(true).open(file_path).unwrap(); + // Optionally, add a newline before the content + file.write_all(b"\n")?; + // Write the content to the file + file.write_all(content.as_bytes())?; + // Update the database and the lists to reflect the new content + self.update_lists(args); + Ok(()) + } + // Search entry list pub fn search_entries(&mut self) { // Use snapshot of entry list saved when starting the search -- cgit v1.2.3 From b892bc9a66e65cb73901d719c83768face67941d Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sun, 15 Dec 2024 12:29:00 +0100 Subject: use specific search command for entering search input mode --- src/app.rs | 19 ++++++++++++++----- src/tui/commands.rs | 14 +++++++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) (limited to 'src/app.rs') diff --git a/src/app.rs b/src/app.rs index a200448..2834e61 100644 --- a/src/app.rs +++ b/src/app.rs @@ -173,23 +173,29 @@ impl App { InputCmdAction::Nothing => {} InputCmdAction::Handle(event) => { self.input.handle_event(&event); - self.bibiman.search_list_by_pattern(&self.input); + if let CurrentArea::SearchArea = self.bibiman.current_area { + self.bibiman.search_list_by_pattern(&self.input); + } } InputCmdAction::Enter => { self.input_mode = true; // Logic for TABS to be added - self.bibiman.enter_search_area(); + // self.bibiman.enter_search_area(); } InputCmdAction::Confirm => { self.input = Input::default(); self.input_mode = false; // Logic for TABS to be added - self.bibiman.confirm_search(); + if let CurrentArea::SearchArea = self.bibiman.current_area { + self.bibiman.confirm_search(); + } } InputCmdAction::Exit => { self.input = Input::default(); self.input_mode = false; - self.bibiman.break_search(); + if let CurrentArea::SearchArea = self.bibiman.current_area { + self.bibiman.break_search(); + } } }, CmdAction::SelectNextRow(amount) => match self.bibiman.current_area { @@ -263,7 +269,10 @@ impl App { CmdAction::ToggleArea => { self.bibiman.toggle_area(); } - CmdAction::SearchList => {} + CmdAction::SearchList => { + self.input_mode = true; + self.bibiman.enter_search_area(); + } CmdAction::Reset => { if let CurrentArea::PopupArea = self.bibiman.current_area { if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { diff --git a/src/tui/commands.rs b/src/tui/commands.rs index 626b11b..f7fd75b 100644 --- a/src/tui/commands.rs +++ b/src/tui/commands.rs @@ -132,16 +132,24 @@ impl From for CmdAction { // Switch selected area KeyCode::Tab => Self::ToggleArea, KeyCode::BackTab => Self::ToggleArea, + // // Enter search mode + // KeyCode::Char('/') => Self::Input(InputCmdAction::Enter), + // KeyCode::Char('f') => { + // if key_event.modifiers == KeyModifiers::CONTROL { + // Self::Input(InputCmdAction::Enter) + // } else { + // Self::Nothing + // } + // } // Enter search mode - KeyCode::Char('/') => Self::Input(InputCmdAction::Enter), + KeyCode::Char('/') => Self::SearchList, KeyCode::Char('f') => { if key_event.modifiers == KeyModifiers::CONTROL { - Self::Input(InputCmdAction::Enter) + Self::SearchList } else { Self::Nothing } } - // KeyCode::Backspace => Self::Input(InputCommand::Resume(Event::Key(key_event))), // Confirm selection KeyCode::Enter => Self::Confirm, // Reset lists/tables -- cgit v1.2.3 From a6fca1fcf164142d84d09242b9d95a1da0b2d2d9 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sun, 15 Dec 2024 13:21:42 +0100 Subject: use input struct, place cursor at pos --- src/app.rs | 99 ++++++++++++++++++++++++++++--------------------- src/bibiman.rs | 4 +- src/tui/popup.rs | 4 +- src/tui/ui.rs | 41 ++++++++++---------- tests/biblatex-test.bib | 13 +++++++ 5 files changed, 95 insertions(+), 66 deletions(-) (limited to 'src/app.rs') diff --git a/src/app.rs b/src/app.rs index 2834e61..bcdef39 100644 --- a/src/app.rs +++ b/src/app.rs @@ -79,47 +79,49 @@ impl App { } else if let Some(PopupKind::MessageError) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup() - } else if let Some(PopupKind::AddEntry) = self.bibiman.popup_area.popup_kind { - // Handle key events for AddEntry popup - match key_event.code { - KeyCode::Char(c) => { - let index = self.bibiman.popup_area.add_entry_cursor_position; - self.bibiman.popup_area.add_entry_input.insert(index, c); - self.bibiman.popup_area.add_entry_cursor_position += 1; - } - KeyCode::Backspace => { - if self.bibiman.popup_area.add_entry_cursor_position > 0 { - self.bibiman.popup_area.add_entry_cursor_position -= 1; - let index = self.bibiman.popup_area.add_entry_cursor_position; - self.bibiman.popup_area.add_entry_input.remove(index); - } - } - KeyCode::Left => { - if self.bibiman.popup_area.add_entry_cursor_position > 0 { - self.bibiman.popup_area.add_entry_cursor_position -= 1; - } - } - KeyCode::Right => { - if self.bibiman.popup_area.add_entry_cursor_position - < self.bibiman.popup_area.add_entry_input.len() - { - self.bibiman.popup_area.add_entry_cursor_position += 1; - } - } - KeyCode::Enter => { - // Handle submission of the new entry - self.bibiman.handle_new_entry_submission(args); - self.bibiman.close_popup(); - self.input_mode = false; - } - KeyCode::Esc => { - // Close the popup without saving - self.bibiman.close_popup(); - self.input_mode = false; - } - _ => {} - } - } else { + } + // else if let Some(PopupKind::AddEntry) = self.bibiman.popup_area.popup_kind { + // // Handle key events for AddEntry popup + // match key_event.code { + // KeyCode::Char(c) => { + // let index = self.bibiman.popup_area.add_entry_cursor_position; + // self.bibiman.popup_area.add_entry_input.insert(index, c); + // self.bibiman.popup_area.add_entry_cursor_position += 1; + // } + // KeyCode::Backspace => { + // if self.bibiman.popup_area.add_entry_cursor_position > 0 { + // self.bibiman.popup_area.add_entry_cursor_position -= 1; + // let index = self.bibiman.popup_area.add_entry_cursor_position; + // self.bibiman.popup_area.add_entry_input.remove(index); + // } + // } + // KeyCode::Left => { + // if self.bibiman.popup_area.add_entry_cursor_position > 0 { + // self.bibiman.popup_area.add_entry_cursor_position -= 1; + // } + // } + // KeyCode::Right => { + // if self.bibiman.popup_area.add_entry_cursor_position + // < self.bibiman.popup_area.add_entry_input.len() + // { + // self.bibiman.popup_area.add_entry_cursor_position += 1; + // } + // } + // KeyCode::Enter => { + // // Handle submission of the new entry + // self.bibiman.handle_new_entry_submission(args, &self.input); + // self.bibiman.close_popup(); + // self.input_mode = false; + // } + // KeyCode::Esc => { + // // Close the popup without saving + // self.bibiman.close_popup(); + // self.input_mode = false; + // } + // _ => {} + // } + // } + else { let command = if self.input_mode { CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) } else { @@ -183,18 +185,28 @@ impl App { // self.bibiman.enter_search_area(); } InputCmdAction::Confirm => { - self.input = Input::default(); - self.input_mode = false; // Logic for TABS to be added if let CurrentArea::SearchArea = self.bibiman.current_area { self.bibiman.confirm_search(); + } else if let CurrentArea::PopupArea = self.bibiman.current_area { + match self.bibiman.popup_area.popup_kind { + Some(PopupKind::AddEntry) => { + self.bibiman.handle_new_entry_submission(args, &self.input); + self.bibiman.close_popup(); + } + _ => {} + } } + self.input = Input::default(); + self.input_mode = false; } InputCmdAction::Exit => { self.input = Input::default(); self.input_mode = false; if let CurrentArea::SearchArea = self.bibiman.current_area { self.bibiman.break_search(); + } else if let CurrentArea::PopupArea = self.bibiman.current_area { + self.bibiman.close_popup(); } } }, @@ -380,6 +392,7 @@ impl App { } } CmdAction::AddEntry => { + self.input_mode = true; self.bibiman.add_entry(); } CmdAction::ShowHelp => { diff --git a/src/bibiman.rs b/src/bibiman.rs index 10dab1e..4e6e5e8 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -123,8 +123,8 @@ impl Bibiman { self.popup_area.popup_kind = Some(PopupKind::AddEntry); } - pub fn handle_new_entry_submission(&mut self, args: &CLIArgs) { - let new_entry_title = self.popup_area.add_entry_input.trim(); + pub fn handle_new_entry_submission(&mut self, args: &CLIArgs, doi_string: &Input) { + let new_entry_title = doi_string.value(); let doi2bib = doi2bib::Doi2Bib::new().unwrap(); let new_entry_future = doi2bib.resolve_doi(new_entry_title); let new_entry = block_on(new_entry_future); diff --git a/src/tui/popup.rs b/src/tui/popup.rs index 9b91801..afe0cfc 100644 --- a/src/tui/popup.rs +++ b/src/tui/popup.rs @@ -40,8 +40,8 @@ pub struct PopupArea { pub popup_scroll_pos: u16, pub popup_list: Vec, pub popup_state: ListState, - pub add_entry_input: String, - pub add_entry_cursor_position: usize, + // pub add_entry_input: String, + // pub add_entry_cursor_position: usize, } impl PopupArea { diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 8d7498a..e3fddfd 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -198,37 +198,39 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { .border_style(Style::new().fg(Color::Indexed(args.colors.entry_color))); // Prepare the input fields - let content = vec![ - Line::from(vec![Span::styled( + let content = vec![Line::from(vec![ + Span::styled( "DOI: ", Style::new().fg(Color::Indexed(args.colors.entry_color)), - )]), - Line::from(app.bibiman.popup_area.add_entry_input.clone()), - ]; + ), + Span::raw(app.input.value().to_string().clone()), + ])]; let paragraph = Paragraph::new(content) .block(block.clone()) .style(Style::new().fg(Color::Indexed(args.colors.main_text_color))) .wrap(Wrap { trim: false }); + let doi_lines = paragraph.line_count(area.width / 2); // Calculate popup size let popup_width = area.width / 2; - let popup_height = 5; // Adjust as needed + let popup_height = doi_lines as u16; // Adjust as needed let popup_area = popup_area(area, popup_width, popup_height); // Render the popup frame.render_widget(Clear, popup_area); + render_cursor(app, frame, popup_area, 6, 2); frame.render_widget(paragraph, popup_area); - // Set the cursor position - if app.input_mode { - // Calculate cursor x and y - let input_prefix_len = "Title: ".len() as u16 + 1; // +1 for padding - let cursor_x = popup_area.x - + input_prefix_len - + app.bibiman.popup_area.add_entry_cursor_position as u16; - let cursor_y = popup_area.y + 1; // Line after 'Title: ' - frame.set_cursor_position(Position::new(cursor_x, cursor_y)); - } + // // Set the cursor position + // if app.input_mode { + // // Calculate cursor x and y + // let input_prefix_len = "Title: ".len() as u16 + 1; // +1 for padding + // let cursor_x = popup_area.x + // + input_prefix_len + // + app.bibiman.popup_area.add_entry_cursor_position as u16; + // let cursor_y = popup_area.y + 1; // Line after 'Title: ' + // frame.set_cursor_position(Position::new(cursor_x, cursor_y)); + // } } Some(PopupKind::MessageConfirm) => { let area = frame.area(); @@ -376,7 +378,7 @@ pub fn render_footer(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rec ])) .block(block); - render_cursor(app, frame, rect, title_lenght + 1); + render_cursor(app, frame, rect, title_lenght + 1, 1); frame.render_widget(search_string, rect); } @@ -1035,12 +1037,13 @@ pub fn render_taglist(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Re } /// Render the cursor when in InputMode -fn render_cursor(app: &mut App, frame: &mut Frame, area: Rect, x_offset: u16) { +fn render_cursor(app: &mut App, frame: &mut Frame, area: Rect, x_offset: u16, y_offset: u16) { let scroll = app.input.visual_scroll(area.width as usize); if app.input_mode { let (x, y) = ( area.x + ((app.input.visual_cursor()).max(scroll) - scroll) as u16 + x_offset, - area.bottom().saturating_sub(1), + // area.bottom().saturating_sub(1) + y_offset, + area.bottom().saturating_sub(y_offset), ); frame.render_widget( Clear, diff --git a/tests/biblatex-test.bib b/tests/biblatex-test.bib index b366fc3..d64b980 100644 --- a/tests/biblatex-test.bib +++ b/tests/biblatex-test.bib @@ -376,3 +376,16 @@ date = {2006}, indextitle = {Palladium pincer complexes}, } + +@article{ + 034780862i9pet, + doi = {10.34780/862I-9PET}, + url = {https://publications.dainst.org/journals/aa/article/view/4444}, + author = {Kammerer-Grothaus, Helke}, + keywords = {Topographie, Grabbauten, Nachleben, Tomba Baccelli}, + language = {de}, + title = {Die ›Tomba Baccelli‹ an der Via Latina in Rom}, + publisher = {Archäologischer Anzeiger}, + year = {2024}, + file = {} +} \ No newline at end of file -- cgit v1.2.3 From bf93bbee1b59c9804a01a7476e12264bbbcf5f40 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 16 Dec 2024 13:46:24 +0100 Subject: rewrite add-entry via DOI workflow + split some functions to fit with different popups + select if append to file (and to which) or create new file + error handling if resolving doi ist not possible + error handling for wront doi patterns --- Cargo.lock | 3 +- Cargo.toml | 1 + src/app.rs | 188 +++++++++++++++++++++++------------------ src/bibiman.rs | 94 +++++++++++---------- src/main.rs | 4 +- src/tui/commands.rs | 10 --- src/tui/popup.rs | 8 +- src/tui/ui.rs | 27 +++--- tests/biblatex-test.bib | 13 --- tests/multi-files/bibfile1.bib | 14 +++ 10 files changed, 191 insertions(+), 171 deletions(-) (limited to 'src/app.rs') diff --git a/Cargo.lock b/Cargo.lock index 8b9dbb7..81b976f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -107,6 +107,7 @@ dependencies = [ "nucleo-matcher", "rand", "ratatui", + "regex", "signal-hook", "tokio", "tokio-util", diff --git a/Cargo.toml b/Cargo.toml index 9d6dba3..34ec65f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,3 +35,4 @@ tokio = { version = "1.39.3", features = ["full"] } tokio-util = "0.7.12" tui-input = "0.11.0" walkdir = "2.5.0" +regex = "1.11.1" diff --git a/src/app.rs b/src/app.rs index bcdef39..a3691d6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -17,15 +17,18 @@ use crate::bibiman::{CurrentArea, FormerArea}; use color_eyre::eyre::{Context, Ok, Result}; +use regex::Regex; // use super::Event; use crate::cliargs::CLIArgs; use crate::tui::commands::InputCmdAction; use crate::tui::popup::PopupKind; use crate::tui::{self, Tui}; use crate::{bibiman::Bibiman, tui::commands::CmdAction}; -use ratatui::crossterm::event::KeyCode; +use core::panic; use std::ffi::OsStr; -use std::path::PathBuf; +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use tui::Event; use tui_input::backend::crossterm::EventHandler; @@ -59,7 +62,7 @@ impl App { }) } - pub async fn run(&mut self, args: &CLIArgs) -> Result<()> { + pub async fn run(&mut self, args: &mut CLIArgs) -> Result<()> { let mut tui = tui::Tui::new()?; tui.enter()?; @@ -80,72 +83,13 @@ impl App { { self.bibiman.close_popup() } - // else if let Some(PopupKind::AddEntry) = self.bibiman.popup_area.popup_kind { - // // Handle key events for AddEntry popup - // match key_event.code { - // KeyCode::Char(c) => { - // let index = self.bibiman.popup_area.add_entry_cursor_position; - // self.bibiman.popup_area.add_entry_input.insert(index, c); - // self.bibiman.popup_area.add_entry_cursor_position += 1; - // } - // KeyCode::Backspace => { - // if self.bibiman.popup_area.add_entry_cursor_position > 0 { - // self.bibiman.popup_area.add_entry_cursor_position -= 1; - // let index = self.bibiman.popup_area.add_entry_cursor_position; - // self.bibiman.popup_area.add_entry_input.remove(index); - // } - // } - // KeyCode::Left => { - // if self.bibiman.popup_area.add_entry_cursor_position > 0 { - // self.bibiman.popup_area.add_entry_cursor_position -= 1; - // } - // } - // KeyCode::Right => { - // if self.bibiman.popup_area.add_entry_cursor_position - // < self.bibiman.popup_area.add_entry_input.len() - // { - // self.bibiman.popup_area.add_entry_cursor_position += 1; - // } - // } - // KeyCode::Enter => { - // // Handle submission of the new entry - // self.bibiman.handle_new_entry_submission(args, &self.input); - // self.bibiman.close_popup(); - // self.input_mode = false; - // } - // KeyCode::Esc => { - // // Close the popup without saving - // self.bibiman.close_popup(); - // self.input_mode = false; - // } - // _ => {} - // } - // } - else { - let command = if self.input_mode { - CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) - } else { - CmdAction::from(key_event) - }; - self.run_command(command, args, &mut tui)? - } + let command = if self.input_mode { + CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) + } else { + CmdAction::from(key_event) + }; + self.run_command(command, args, &mut tui)? } - // Event::Key(key_event) => { - // // Automatically close message popups on next keypress - // if let Some(PopupKind::MessageConfirm) = self.bibiman.popup_area.popup_kind { - // self.bibiman.close_popup() - // } else if let Some(PopupKind::MessageError) = self.bibiman.popup_area.popup_kind - // { - // self.bibiman.close_popup() - // } - - // let command = if self.input_mode { - // CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) - // } else { - // CmdAction::from(key_event) - // }; - // self.run_command(command, args, &mut tui)? - // } Event::Mouse(mouse_event) => { self.run_command(CmdAction::from(mouse_event), args, &mut tui)? } @@ -169,7 +113,7 @@ impl App { self.running = false; } - pub fn run_command(&mut self, cmd: CmdAction, args: &CLIArgs, tui: &mut Tui) -> Result<()> { + pub fn run_command(&mut self, cmd: CmdAction, args: &mut CLIArgs, tui: &mut Tui) -> Result<()> { match cmd { CmdAction::Input(cmd) => match cmd { InputCmdAction::Nothing => {} @@ -191,8 +135,19 @@ impl App { } else if let CurrentArea::PopupArea = self.bibiman.current_area { match self.bibiman.popup_area.popup_kind { Some(PopupKind::AddEntry) => { - self.bibiman.handle_new_entry_submission(args, &self.input); + let doi = self.input.value(); self.bibiman.close_popup(); + self.input_mode = false; + // Check if the DOI pattern is valid. If not, show warning and break + if doi.starts_with("10.") || doi.starts_with("http") { + self.bibiman.handle_new_entry_submission(args, doi); + } else { + self.bibiman.popup_area.popup_message( + "No valid DOI pattern: ", + doi, + false, + ); + } } _ => {} } @@ -218,13 +173,15 @@ impl App { CurrentArea::TagArea => { self.bibiman.select_next_tag(amount); } - CurrentArea::PopupArea => { - if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { + CurrentArea::PopupArea => match self.bibiman.popup_area.popup_kind { + Some(PopupKind::Help) => { self.bibiman.popup_area.popup_scroll_down(); - } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind { + } + Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => { self.bibiman.popup_area.popup_state.scroll_down_by(1) } - } + _ => {} + }, _ => {} }, CmdAction::SelectPrevRow(amount) => match self.bibiman.current_area { @@ -235,13 +192,15 @@ impl App { CurrentArea::TagArea => { self.bibiman.select_previous_tag(amount); } - CurrentArea::PopupArea => { - if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { + CurrentArea::PopupArea => match self.bibiman.popup_area.popup_kind { + Some(PopupKind::Help) => { self.bibiman.popup_area.popup_scroll_up(); - } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind { + } + Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => { self.bibiman.popup_area.popup_state.scroll_up_by(1) } - } + _ => {} + }, _ => {} }, CmdAction::SelectNextCol => { @@ -290,8 +249,10 @@ impl App { if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { self.bibiman.popup_area.popup_scroll_pos = 0; self.bibiman.close_popup() - } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind { + } else if let Some(PopupKind::SelectRes) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup() + } else if let Some(PopupKind::SelectFile) = self.bibiman.popup_area.popup_kind { + self.bibiman.close_popup(); } } else { self.bibiman.reset_current_list(); @@ -303,7 +264,7 @@ impl App { } else if let CurrentArea::PopupArea = self.bibiman.current_area { if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup(); - } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind { + } else if let Some(PopupKind::SelectRes) = self.bibiman.popup_area.popup_kind { // Index of selected entry let entry_idx = self .bibiman @@ -330,6 +291,55 @@ impl App { }; // run command to open file/Url self.bibiman.close_popup() + } else if let Some(PopupKind::SelectFile) = self.bibiman.popup_area.popup_kind { + // Index of selected popup field + let popup_idx = self.bibiman.popup_area.popup_state.selected().unwrap(); + + // regex pattern to match citekey in fetched bibtexstring + let pattern = Regex::new(r"\{([^\{\},]*),").unwrap(); + + let citekey = PathBuf::from( + pattern + .captures(&self.bibiman.popup_area.popup_sel_item) + .unwrap() + .get(1) + .unwrap() + .as_str(), + ); + + // Check if new file or existing file was choosen + let mut file = if self.bibiman.popup_area.popup_list[popup_idx] + .contains("Create new file") + { + // Get path of current files + let path: PathBuf = if args.files[0].is_file() { + args.files[0].parent().unwrap().to_owned() + } else { + dirs::home_dir().unwrap() // home dir as fallback + }; + + let citekey = citekey.with_extension("bib"); + + let newfile = path.join(citekey); + + args.files.push(newfile.clone()); + + File::create_new(newfile).unwrap() + } else { + let file_path = &args.files[popup_idx - 1]; + OpenOptions::new().append(true).open(file_path).unwrap() + }; + // Optionally, add a newline before the content + file.write_all(b"\n")?; + // Write content to file + file.write_all(self.bibiman.popup_area.popup_sel_item.as_bytes())?; + // Update the database and the lists to reflect the new content + self.bibiman.update_lists(args); + self.bibiman.close_popup(); + + // Select newly created entry + self.bibiman + .select_entry_by_citekey(citekey.to_str().unwrap()); } } } @@ -378,6 +388,7 @@ impl App { if entry.filepath.is_some() { items.push("File (PDF/EPUB)".to_owned()) } + self.bibiman.popup_area.popup_kind = Some(PopupKind::SelectRes); self.bibiman.popup_area.popup_selection(items); self.bibiman.former_area = Some(FormerArea::EntryArea); self.bibiman.current_area = CurrentArea::PopupArea; @@ -392,8 +403,10 @@ impl App { } } CmdAction::AddEntry => { - self.input_mode = true; - self.bibiman.add_entry(); + if let CurrentArea::EntryArea = self.bibiman.current_area { + self.input_mode = true; + self.bibiman.add_entry(); + } } CmdAction::ShowHelp => { self.bibiman.show_help(); @@ -498,4 +511,15 @@ mod test { assert_eq!(path, PathBuf::from(full_path)) } + + #[test] + fn regex_capture_citekey() { + let re = Regex::new(r"\{([^\{\},]*),").unwrap(); + + let bibstring = String::from("@article{citekey77_2001:!?, author = {Hanks, Tom}, title = {A great book}, year = {2001}}"); + + let result = re.captures(&bibstring).unwrap(); + + assert_eq!(result.get(1).unwrap().as_str(), "citekey77_2001:!?") + } } diff --git a/src/bibiman.rs b/src/bibiman.rs index 4e6e5e8..0dc64e0 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -22,7 +22,7 @@ use crate::tui::popup::{PopupArea, PopupKind}; use crate::tui::Tui; use crate::{bibiman::entries::EntryTable, bibiman::keywords::TagList}; use arboard::Clipboard; -use color_eyre::eyre::Result; +use color_eyre::eyre::{eyre, Result}; use doi2bib; use editor_command::EditorBuilder; use futures::executor::block_on; @@ -115,32 +115,32 @@ impl Bibiman { pub fn add_entry(&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::AddEntry); } - pub fn handle_new_entry_submission(&mut self, args: &CLIArgs, doi_string: &Input) { - let new_entry_title = doi_string.value(); + ///Try to resolve entered DOI. If successfull, choose file where to append + ///the new entry via `append_to_file()` function. If not, show error popup + /// + ///The method needs two arguments: the CLIArgs struct and the `str` containing the DOI + pub fn handle_new_entry_submission(&mut self, args: &CLIArgs, doi_string: &str) { let doi2bib = doi2bib::Doi2Bib::new().unwrap(); - let new_entry_future = doi2bib.resolve_doi(new_entry_title); + let new_entry_future = doi2bib.resolve_doi(doi_string); let new_entry = block_on(new_entry_future); if let Ok(entry) = new_entry { - // TODO: Add error handling for failed insert - let formatted_content = Self::format_bibtex_entry(&entry, ""); - - if self.append_to_file(args, &formatted_content).is_err() { - self.popup_area.popup_kind = Some(PopupKind::MessageError); - self.popup_area.popup_message = "Failed to add new entry".to_string(); - } - // TODO: Add error handling for failed DOI lookup + // Save generated bibtex entry in structs field + self.popup_area.popup_sel_item = entry; + self.popup_area.popup_kind = Some(PopupKind::SelectFile); + self.append_to_file(args); + self.former_area = Some(FormerArea::EntryArea); + self.current_area = CurrentArea::PopupArea; + self.popup_area.popup_state.select(Some(0)) } else { - self.popup_area.popup_kind = Some(PopupKind::MessageError); - self.popup_area.popup_message = "Failed to add new entry".to_string(); + self.popup_area + .popup_message("Failed to add new entry", "", false); } } @@ -336,6 +336,26 @@ impl Bibiman { } } + pub fn select_entry_by_citekey(&mut self, citekey: &str) { + // Search for entry by matching citekeys + let mut idx_count = 0; + loop { + if idx_count == self.entry_table.entry_table_items.len() { + idx_count = 0; + break; + } else if self.entry_table.entry_table_items[idx_count] + .citekey + .contains(citekey) + { + break; + } + idx_count += 1 + } + + // Set selected entry to vec-index of match + self.entry_table.entry_table_state.select(Some(idx_count)); + } + pub fn run_editor(&mut self, args: &CLIArgs, tui: &mut Tui) -> Result<()> { // get filecontent and citekey for calculating line number let citekey: &str = &self.entry_table.entry_table_items @@ -404,38 +424,22 @@ impl Bibiman { // Update the database and the lists to show changes Self::update_lists(self, args); - // Search for entry, selected before editing, by matching citekeys - // Use earlier saved copy of citekey to match - let mut idx_count = 0; - loop { - if self.entry_table.entry_table_items[idx_count] - .citekey - .contains(citekey) - { - break; - } - idx_count += 1 - } - - // Set selected entry to vec-index of match - self.entry_table.entry_table_state.select(Some(idx_count)); + // Select entry which was selected before entering editor + self.select_entry_by_citekey(citekey); Ok(()) } - pub fn append_to_file(&mut self, args: &CLIArgs, content: &str) -> Result<()> { - // Determine the file path to append to - let file_path = args.files.first().unwrap(); - // Open the file in append mode - let mut file = OpenOptions::new().append(true).open(file_path).unwrap(); - // Optionally, add a newline before the content - file.write_all(b"\n")?; - // Format the content - // Write the formatted content to the file - file.write_all(content.as_bytes())?; - // Update the database and the lists to reflect the new content - self.update_lists(args); - Ok(()) + pub fn append_to_file(&mut self, args: &CLIArgs) { + let mut items = vec!["Create new file".to_owned()]; + if args.files.len() > 1 { + for f in args.files.clone() { + items.push(f.to_str().unwrap().to_owned()); + } + } else { + items.push(args.files.first().unwrap().to_str().unwrap().to_owned()); + } + self.popup_area.popup_selection(items); } /// Formats a raw BibTeX entry string for better readability. @@ -448,7 +452,7 @@ impl Bibiman { let preamble = &entry[..start_brace_pos + 1]; let preamble = preamble.trim_start(); formatted.push_str(preamble); - formatted.push('\n'); // Add newline + // formatted.push('\n'); // Add newline // Get the content inside the braces let rest = &entry[start_brace_pos + 1..]; diff --git a/src/main.rs b/src/main.rs index b1160e2..78c5075 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ pub mod tui; #[tokio::main] async fn main() -> Result<()> { // Parse CLI arguments - let parsed_args = CLIArgs::parse_args().unwrap(); + let mut parsed_args = CLIArgs::parse_args().unwrap(); // Print help if -h/--help flag is passed and exit if parsed_args.helparg { @@ -48,6 +48,6 @@ async fn main() -> Result<()> { // Create an application. let mut app = App::new(&parsed_args)?; - app.run(&parsed_args).await?; + app.run(&mut parsed_args).await?; Ok(()) } diff --git a/src/tui/commands.rs b/src/tui/commands.rs index f7fd75b..0e00f95 100644 --- a/src/tui/commands.rs +++ b/src/tui/commands.rs @@ -112,7 +112,6 @@ impl From for CmdAction { Self::SelectPrevRow(5) } else { Self::Nothing - // Self::Open(OpenRessource::WebLink) } } // Scroll info/preview area @@ -132,15 +131,6 @@ impl From for CmdAction { // Switch selected area KeyCode::Tab => Self::ToggleArea, KeyCode::BackTab => Self::ToggleArea, - // // Enter search mode - // KeyCode::Char('/') => Self::Input(InputCmdAction::Enter), - // KeyCode::Char('f') => { - // if key_event.modifiers == KeyModifiers::CONTROL { - // Self::Input(InputCmdAction::Enter) - // } else { - // Self::Nothing - // } - // } // Enter search mode KeyCode::Char('/') => Self::SearchList, KeyCode::Char('f') => { diff --git a/src/tui/popup.rs b/src/tui/popup.rs index afe0cfc..352b328 100644 --- a/src/tui/popup.rs +++ b/src/tui/popup.rs @@ -28,7 +28,8 @@ pub enum PopupKind { Help, MessageConfirm, MessageError, - Selection, + SelectRes, + SelectFile, AddEntry, } @@ -40,6 +41,7 @@ pub struct PopupArea { pub popup_scroll_pos: u16, pub popup_list: Vec, pub popup_state: ListState, + pub popup_sel_item: String, // pub add_entry_input: String, // pub add_entry_cursor_position: usize, } @@ -51,7 +53,6 @@ impl PopupArea { ("TAB: ", "Toggle areas (Entries, Keywords)"), ("/|Ctrl+f: ", "Enter search mode"), ("q|Ctrl+c: ", "Quit bibiman"), - ("a: ", "Add new entry"), ("?: ", "Show help"), ("Entry Table", "sub"), ("j,k|↓,↑: ", "Select next/previous entry"), @@ -64,6 +65,7 @@ impl PopupArea { ("e: ", "Open editor at selected entry"), ("o: ", "Open with selected entry associated PDF"), ("u: ", "Open DOI/URL of selected entry"), + ("a: ", "Add new entry"), ("ESC: ", "Reset all lists"), ("Keyword List", "sub"), ("j,k|↓,↑: ", "Select next/previous item"), @@ -123,7 +125,7 @@ impl PopupArea { pub fn popup_selection(&mut self, items: Vec) { self.popup_list = items; - self.popup_kind = Some(PopupKind::Selection); + // self.popup_kind = Some(PopupKind::SelectRes); self.is_popup = true; } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index e3fddfd..6a3b8de 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -212,25 +212,14 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { let doi_lines = paragraph.line_count(area.width / 2); // Calculate popup size - let popup_width = area.width / 2; + let popup_width = area.width / 4 * 3; let popup_height = doi_lines as u16; // Adjust as needed let popup_area = popup_area(area, popup_width, popup_height); // Render the popup frame.render_widget(Clear, popup_area); - render_cursor(app, frame, popup_area, 6, 2); + render_cursor(app, frame, popup_area, 6, doi_lines as u16 - 1); frame.render_widget(paragraph, popup_area); - - // // Set the cursor position - // if app.input_mode { - // // Calculate cursor x and y - // let input_prefix_len = "Title: ".len() as u16 + 1; // +1 for padding - // let cursor_x = popup_area.x - // + input_prefix_len - // + app.bibiman.popup_area.add_entry_cursor_position as u16; - // let cursor_y = popup_area.y + 1; // Line after 'Title: ' - // frame.set_cursor_position(Position::new(cursor_x, cursor_y)); - // } } Some(PopupKind::MessageConfirm) => { let area = frame.area(); @@ -294,7 +283,7 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { frame.render_widget(Clear, popup_area); frame.render_widget(&content, popup_area) } - Some(PopupKind::Selection) => { + Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => { let list_items: Vec = app .bibiman .popup_area @@ -303,8 +292,16 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { .map(|item| ListItem::from(item.to_owned())) .collect(); + let title = if let Some(PopupKind::SelectRes) = app.bibiman.popup_area.popup_kind { + " Open " + } else if let Some(PopupKind::SelectFile) = app.bibiman.popup_area.popup_kind { + " Select file to append entry " + } else { + " Select " + }; + let block = Block::bordered() - .title_top(" Open ".bold()) + .title_top(title.bold()) .title_bottom(" (j,k|↓,↑) ━ (ENTER) ━ (ESC) ".bold()) .title_alignment(Alignment::Center) .style( diff --git a/tests/biblatex-test.bib b/tests/biblatex-test.bib index d64b980..b366fc3 100644 --- a/tests/biblatex-test.bib +++ b/tests/biblatex-test.bib @@ -376,16 +376,3 @@ date = {2006}, indextitle = {Palladium pincer complexes}, } - -@article{ - 034780862i9pet, - doi = {10.34780/862I-9PET}, - url = {https://publications.dainst.org/journals/aa/article/view/4444}, - author = {Kammerer-Grothaus, Helke}, - keywords = {Topographie, Grabbauten, Nachleben, Tomba Baccelli}, - language = {de}, - title = {Die ›Tomba Baccelli‹ an der Via Latina in Rom}, - publisher = {Archäologischer Anzeiger}, - year = {2024}, - file = {} -} \ No newline at end of file diff --git a/tests/multi-files/bibfile1.bib b/tests/multi-files/bibfile1.bib index 31d81bc..230a517 100644 --- a/tests/multi-files/bibfile1.bib +++ b/tests/multi-files/bibfile1.bib @@ -11,3 +11,17 @@ langidopts = {variant=american}, annotation = {A \texttt{collection} entry providing the excerpt information for the \texttt{doody} entry. Note the format of the \texttt{pages} field}, } + +@book{ + Bernal_2001, + title={Black Athena Writes Back: Martin Bernal Responds to His Critics}, + ISBN={9780822380078}, + url={http://dx.doi.org/10.1515/9780822380078}, + DOI={10.1515/9780822380078}, + publisher={Duke University Press}, + author={Bernal, Martin}, + editor={Moore, David Chioni}, + year={2001}, + month=sep, + file = {} +} \ No newline at end of file -- cgit v1.2.3 From 6ff6b82e0fcea4344db8b17ea5be2d72b3d9d9f2 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 23 Dec 2024 19:07:24 +0100 Subject: better error messages for doi-add --- README.md | 2 +- src/app.rs | 42 ++++++++++++++++++++++++------------------ src/bibiman.rs | 4 ++-- src/tui/popup.rs | 4 ++-- src/tui/ui.rs | 6 +++--- 5 files changed, 32 insertions(+), 26 deletions(-) (limited to 'src/app.rs') diff --git a/README.md b/README.md index bf1749e..069b6d4 100644 --- a/README.md +++ b/README.md @@ -116,8 +116,8 @@ my personal workflow. There are more to come, the list will be updated: - [x] **Scrollbar** for better navigating. - [x] **Sort Entries** by column (`Authors`, `Title`, `Year`, `Pubtype`) - [x] **Load multiple files** into one session. +- [x] **Add Entry via DOI** as formatted code. - [ ] **Open related notes file** for specific entry. -- [ ] **Add Entry via DOI** as formatted code. - [ ] **Implement config file** for setting some default values like main bibfile, PDF-opener, or editor - [ ] **Support Hayagriva(`.yaml`)** format as input (_on hold for now_, because diff --git a/src/app.rs b/src/app.rs index a3691d6..a3b0522 100644 --- a/src/app.rs +++ b/src/app.rs @@ -139,7 +139,12 @@ impl App { self.bibiman.close_popup(); self.input_mode = false; // Check if the DOI pattern is valid. If not, show warning and break - if doi.starts_with("10.") || doi.starts_with("http") { + if doi.starts_with("10.") + || doi.starts_with("https://doi.org") + || doi.starts_with("https://dx.doi.org") + || doi.starts_with("http://doi.org") + || doi.starts_with("http://dx.doi.org") + { self.bibiman.handle_new_entry_submission(args, doi); } else { self.bibiman.popup_area.popup_message( @@ -177,7 +182,7 @@ impl App { Some(PopupKind::Help) => { self.bibiman.popup_area.popup_scroll_down(); } - Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => { + Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) => { self.bibiman.popup_area.popup_state.scroll_down_by(1) } _ => {} @@ -196,7 +201,7 @@ impl App { Some(PopupKind::Help) => { self.bibiman.popup_area.popup_scroll_up(); } - Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => { + Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) => { self.bibiman.popup_area.popup_state.scroll_up_by(1) } _ => {} @@ -249,9 +254,10 @@ impl App { if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { self.bibiman.popup_area.popup_scroll_pos = 0; self.bibiman.close_popup() - } else if let Some(PopupKind::SelectRes) = self.bibiman.popup_area.popup_kind { + } else if let Some(PopupKind::OpenRes) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup() - } else if let Some(PopupKind::SelectFile) = self.bibiman.popup_area.popup_kind { + } else if let Some(PopupKind::AppendToFile) = self.bibiman.popup_area.popup_kind + { self.bibiman.close_popup(); } } else { @@ -264,7 +270,7 @@ impl App { } else if let CurrentArea::PopupArea = self.bibiman.current_area { if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup(); - } else if let Some(PopupKind::SelectRes) = self.bibiman.popup_area.popup_kind { + } else if let Some(PopupKind::OpenRes) = self.bibiman.popup_area.popup_kind { // Index of selected entry let entry_idx = self .bibiman @@ -291,26 +297,27 @@ impl App { }; // run command to open file/Url self.bibiman.close_popup() - } else if let Some(PopupKind::SelectFile) = self.bibiman.popup_area.popup_kind { + } else if let Some(PopupKind::AppendToFile) = self.bibiman.popup_area.popup_kind + { // Index of selected popup field let popup_idx = self.bibiman.popup_area.popup_state.selected().unwrap(); // regex pattern to match citekey in fetched bibtexstring let pattern = Regex::new(r"\{([^\{\},]*),").unwrap(); - let citekey = PathBuf::from( - pattern - .captures(&self.bibiman.popup_area.popup_sel_item) - .unwrap() - .get(1) - .unwrap() - .as_str(), - ); + let citekey = pattern + .captures(&self.bibiman.popup_area.popup_sel_item) + .unwrap() + .get(1) + .unwrap() + .as_str() + .to_string(); // Check if new file or existing file was choosen let mut file = if self.bibiman.popup_area.popup_list[popup_idx] .contains("Create new file") { + let citekey = PathBuf::from(&citekey); // Get path of current files let path: PathBuf = if args.files[0].is_file() { args.files[0].parent().unwrap().to_owned() @@ -338,8 +345,7 @@ impl App { self.bibiman.close_popup(); // Select newly created entry - self.bibiman - .select_entry_by_citekey(citekey.to_str().unwrap()); + self.bibiman.select_entry_by_citekey(&citekey); } } } @@ -388,7 +394,7 @@ impl App { if entry.filepath.is_some() { items.push("File (PDF/EPUB)".to_owned()) } - self.bibiman.popup_area.popup_kind = Some(PopupKind::SelectRes); + self.bibiman.popup_area.popup_kind = Some(PopupKind::OpenRes); self.bibiman.popup_area.popup_selection(items); self.bibiman.former_area = Some(FormerArea::EntryArea); self.bibiman.current_area = CurrentArea::PopupArea; diff --git a/src/bibiman.rs b/src/bibiman.rs index 0dc64e0..5d6aa30 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -133,14 +133,14 @@ impl Bibiman { if let Ok(entry) = new_entry { // Save generated bibtex entry in structs field self.popup_area.popup_sel_item = entry; - self.popup_area.popup_kind = Some(PopupKind::SelectFile); + self.popup_area.popup_kind = Some(PopupKind::AppendToFile); self.append_to_file(args); self.former_area = Some(FormerArea::EntryArea); self.current_area = CurrentArea::PopupArea; self.popup_area.popup_state.select(Some(0)) } else { self.popup_area - .popup_message("Failed to add new entry", "", false); + .popup_message("Can't find DOI: ", &doi_string, false); } } diff --git a/src/tui/popup.rs b/src/tui/popup.rs index 352b328..78a0719 100644 --- a/src/tui/popup.rs +++ b/src/tui/popup.rs @@ -28,8 +28,8 @@ pub enum PopupKind { Help, MessageConfirm, MessageError, - SelectRes, - SelectFile, + OpenRes, + AppendToFile, AddEntry, } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 6a3b8de..4f64338 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -283,7 +283,7 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { frame.render_widget(Clear, popup_area); frame.render_widget(&content, popup_area) } - Some(PopupKind::SelectRes) | Some(PopupKind::SelectFile) => { + Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) => { let list_items: Vec = app .bibiman .popup_area @@ -292,9 +292,9 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) { .map(|item| ListItem::from(item.to_owned())) .collect(); - let title = if let Some(PopupKind::SelectRes) = app.bibiman.popup_area.popup_kind { + let title = if let Some(PopupKind::OpenRes) = app.bibiman.popup_area.popup_kind { " Open " - } else if let Some(PopupKind::SelectFile) = app.bibiman.popup_area.popup_kind { + } else if let Some(PopupKind::AppendToFile) = app.bibiman.popup_area.popup_kind { " Select file to append entry " } else { " Select " -- cgit v1.2.3 From 8333136cb770cbfbb7be2160fd85687493d96ea4 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 23 Dec 2024 20:41:55 +0100 Subject: collect code for adding entries and opening files in method --- src/app.rs | 91 ++--------------------------------------------- src/bibiman.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++-- tests/biblatex-test.bib | 2 +- 3 files changed, 95 insertions(+), 92 deletions(-) (limited to 'src/app.rs') diff --git a/src/app.rs b/src/app.rs index a3b0522..2240e8f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -17,7 +17,6 @@ use crate::bibiman::{CurrentArea, FormerArea}; use color_eyre::eyre::{Context, Ok, Result}; -use regex::Regex; // use super::Event; use crate::cliargs::CLIArgs; use crate::tui::commands::InputCmdAction; @@ -26,9 +25,7 @@ use crate::tui::{self, Tui}; use crate::{bibiman::Bibiman, tui::commands::CmdAction}; use core::panic; use std::ffi::OsStr; -use std::fs::{File, OpenOptions}; -use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process::{Command, Stdio}; use tui::Event; use tui_input::backend::crossterm::EventHandler; @@ -271,81 +268,10 @@ impl App { if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup(); } else if let Some(PopupKind::OpenRes) = self.bibiman.popup_area.popup_kind { - // Index of selected entry - let entry_idx = self - .bibiman - .entry_table - .entry_table_state - .selected() - .unwrap(); - - // Index of selected popup field - let popup_idx = self.bibiman.popup_area.popup_state.selected().unwrap(); - - // Choose ressource depending an selected popup field - if self.bibiman.popup_area.popup_list[popup_idx].contains("Weblink") { - let object = - self.bibiman.entry_table.entry_table_items[entry_idx].doi_url(); - let url = prepare_weblink(object); - open_connected_link(&url)?; - } else if self.bibiman.popup_area.popup_list[popup_idx].contains("File") { - let object = - self.bibiman.entry_table.entry_table_items[entry_idx].filepath(); - open_connected_file(object)?; - } else { - eprintln!("Unable to find ressource to open"); - }; - // run command to open file/Url - self.bibiman.close_popup() + self.bibiman.open_connected_res()?; } else if let Some(PopupKind::AppendToFile) = self.bibiman.popup_area.popup_kind { - // Index of selected popup field - let popup_idx = self.bibiman.popup_area.popup_state.selected().unwrap(); - - // regex pattern to match citekey in fetched bibtexstring - let pattern = Regex::new(r"\{([^\{\},]*),").unwrap(); - - let citekey = pattern - .captures(&self.bibiman.popup_area.popup_sel_item) - .unwrap() - .get(1) - .unwrap() - .as_str() - .to_string(); - - // Check if new file or existing file was choosen - let mut file = if self.bibiman.popup_area.popup_list[popup_idx] - .contains("Create new file") - { - let citekey = PathBuf::from(&citekey); - // Get path of current files - let path: PathBuf = if args.files[0].is_file() { - args.files[0].parent().unwrap().to_owned() - } else { - dirs::home_dir().unwrap() // home dir as fallback - }; - - let citekey = citekey.with_extension("bib"); - - let newfile = path.join(citekey); - - args.files.push(newfile.clone()); - - File::create_new(newfile).unwrap() - } else { - let file_path = &args.files[popup_idx - 1]; - OpenOptions::new().append(true).open(file_path).unwrap() - }; - // Optionally, add a newline before the content - file.write_all(b"\n")?; - // Write content to file - file.write_all(self.bibiman.popup_area.popup_sel_item.as_bytes())?; - // Update the database and the lists to reflect the new content - self.bibiman.update_lists(args); - self.bibiman.close_popup(); - - // Select newly created entry - self.bibiman.select_entry_by_citekey(&citekey); + self.bibiman.append_entry_to_file(args)? } } } @@ -517,15 +443,4 @@ mod test { assert_eq!(path, PathBuf::from(full_path)) } - - #[test] - fn regex_capture_citekey() { - let re = Regex::new(r"\{([^\{\},]*),").unwrap(); - - let bibstring = String::from("@article{citekey77_2001:!?, author = {Hanks, Tom}, title = {A great book}, year = {2001}}"); - - let result = re.captures(&bibstring).unwrap(); - - assert_eq!(result.get(1).unwrap().as_str(), "citekey77_2001:!?") - } } diff --git a/src/bibiman.rs b/src/bibiman.rs index 5d6aa30..754e8f6 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . ///// +use crate::app; use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::{bibisetup::*, search::BibiSearch}; use crate::cliargs::CLIArgs; @@ -22,14 +23,16 @@ use crate::tui::popup::{PopupArea, PopupKind}; use crate::tui::Tui; use crate::{bibiman::entries::EntryTable, bibiman::keywords::TagList}; use arboard::Clipboard; -use color_eyre::eyre::{eyre, Result}; +use color_eyre::eyre::Result; use doi2bib; use editor_command::EditorBuilder; use futures::executor::block_on; use ratatui::widgets::ScrollbarState; +use regex::Regex; use std::fs; -use std::fs::OpenOptions; +use std::fs::{File, OpenOptions}; use std::io::Write; +use std::path::PathBuf; use std::process::Command; use std::result::Result::Ok; use tui_input::Input; @@ -442,6 +445,80 @@ impl Bibiman { self.popup_area.popup_selection(items); } + pub fn append_entry_to_file(&mut self, args: &mut CLIArgs) -> Result<()> { + // Index of selected popup field + let popup_idx = self.popup_area.popup_state.selected().unwrap(); + + // regex pattern to match citekey in fetched bibtexstring + let pattern = Regex::new(r"\{([^\{\},]*),").unwrap(); + + let citekey = pattern + .captures(&self.popup_area.popup_sel_item) + .unwrap() + .get(1) + .unwrap() + .as_str() + .to_string(); + + // Check if new file or existing file was choosen + let mut file = if self.popup_area.popup_list[popup_idx].contains("Create new file") { + let citekey = PathBuf::from(&citekey); + // Get path of current files + let path: PathBuf = if args.files[0].is_file() { + args.files[0].parent().unwrap().to_owned() + } else { + dirs::home_dir().unwrap() // home dir as fallback + }; + + let citekey = citekey.with_extension("bib"); + + let newfile = path.join(citekey); + + args.files.push(newfile.clone()); + + File::create_new(newfile).unwrap() + } else { + let file_path = &args.files[popup_idx - 1]; + OpenOptions::new().append(true).open(file_path).unwrap() + }; + // Optionally, add a newline before the content + file.write_all(b"\n")?; + // Write content to file + file.write_all(self.popup_area.popup_sel_item.as_bytes())?; + // Update the database and the lists to reflect the new content + self.update_lists(args); + self.close_popup(); + + // Select newly created entry + self.select_entry_by_citekey(&citekey); + + Ok(()) + } + + pub fn open_connected_res(&mut self) -> Result<()> { + // Index of selected entry + let entry_idx = self.entry_table.entry_table_state.selected().unwrap(); + + // Index of selected popup field + let popup_idx = self.popup_area.popup_state.selected().unwrap(); + + // Choose ressource depending an selected popup field + if self.popup_area.popup_list[popup_idx].contains("Weblink") { + let object = self.entry_table.entry_table_items[entry_idx].doi_url(); + let url = app::prepare_weblink(object); + app::open_connected_link(&url)?; + } else if self.popup_area.popup_list[popup_idx].contains("File") { + let object = self.entry_table.entry_table_items[entry_idx].filepath(); + app::open_connected_file(object)?; + } else { + eprintln!("Unable to find ressource to open"); + }; + // run command to open file/Url + self.close_popup(); + + Ok(()) + } + /// Formats a raw BibTeX entry string for better readability. pub fn format_bibtex_entry(entry: &str, file_path: &str) -> String { let mut formatted = String::new(); @@ -757,7 +834,7 @@ impl Bibiman { #[cfg(test)] mod tests { - // use super::*; + use super::*; #[test] fn citekey_pattern() { @@ -765,4 +842,15 @@ mod tests { assert_eq!(citekey, "{a_key_2001,") } + + #[test] + fn regex_capture_citekey() { + let re = Regex::new(r"\{([^\{\},]*),").unwrap(); + + let bibstring = String::from("@article{citekey77_2001:!?, author = {Hanks, Tom}, title = {A great book}, year = {2001}}"); + + let result = re.captures(&bibstring).unwrap(); + + assert_eq!(result.get(1).unwrap().as_str(), "citekey77_2001:!?") + } } diff --git a/tests/biblatex-test.bib b/tests/biblatex-test.bib index b366fc3..d0fc0a6 100644 --- a/tests/biblatex-test.bib +++ b/tests/biblatex-test.bib @@ -65,7 +65,7 @@ author = {Aristotle}, location = {New York}, publisher = {G. P. Putnam}, - url = {infobooks.org/authors/classic/aristotle-books/#Physic}, + url = {https://www.infobooks.org/authors/classic/aristotle-books/#Physic}, date = {1929}, translator = {Wicksteed, P. H. and Cornford, F. M.}, keywords = {primary, ancient, philosophy}, -- cgit v1.2.3