From 40629e0c2dc62bfb1786cb1c18bc68ed4c23e9ac Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sun, 29 Jun 2025 14:20:10 +0200 Subject: working note-opening action. needs refinement: errors and ui --- src/bibiman.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/bibiman.rs') diff --git a/src/bibiman.rs b/src/bibiman.rs index 21601e3..96a733c 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -692,6 +692,21 @@ impl Bibiman { None, )?; } + } + if self.popup_area.popup_list[popup_idx].0.contains("Note") { + let file = expand_home(&PathBuf::from(popup_entry.clone())); + // let object: OsString = popup_entry.into(); + if file.is_file() { + app::open_connected_file(cfg, &file.into_os_string())?; + self.close_popup(); + } else { + self.open_popup( + PopupKind::MessageError, + Some("No valid file path: "), + Some(file.to_str().unwrap()), + None, + )?; + } } else { eprintln!("Unable to find ressource to open"); }; -- cgit v1.2.3 From ae1667410b0a812fff8d464251548f23f88ae024 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sun, 29 Jun 2025 20:31:41 +0200 Subject: some more tests for notes, need to elaborate function for opening notes --- src/app.rs | 22 +++++++++++++--------- src/bibiman.rs | 5 ++--- tests/note-files/aristotle:poetics.txt | 0 tests/note-files/aristotle:rhetoric.md | 0 tests/note-files/bertram.txt | 1 + tests/note-files/betram.txt | 1 - tests/test-config.toml | 2 +- 7 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 tests/note-files/aristotle:poetics.txt create mode 100644 tests/note-files/aristotle:rhetoric.md create mode 100644 tests/note-files/bertram.txt delete mode 100644 tests/note-files/betram.txt (limited to 'src/bibiman.rs') diff --git a/src/app.rs b/src/app.rs index 5f2d16b..f015494 100644 --- a/src/app.rs +++ b/src/app.rs @@ -410,15 +410,6 @@ impl App { pub fn open_connected_file(cfg: &BibiConfig, file: &OsStr) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard let cmd = &cfg.general.pdf_opener; - // If necessary, replace ~ with /home dir - // let file = if cfg.general.file_prefix.is_some() { - // cfg.general.file_prefix.clone().unwrap().join(file) - // } else { - // PathBuf::from(file) - // }; - // let file = PathBuf::from(file); - - // let file = expand_home(&file).into_os_string(); // Pass filepath as argument, pipe stdout and stderr to /dev/null // to keep the TUI clean (where is it piped on Windows???) @@ -432,6 +423,19 @@ pub fn open_connected_file(cfg: &BibiConfig, file: &OsStr) -> Result<()> { Ok(()) } +pub fn open_connected_note(cfg: &BibiConfig, file: &OsStr) -> Result<()> { + // let cmd = cfg.general.editor.as_ref().unwrap(); + + let _ = Command::new("xdg-open") + .arg(file) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .wrap_err("Opening file not possible"); + + Ok(()) +} + pub fn open_connected_link(cfg: &BibiConfig, link: &str) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard let cmd = &cfg.general.url_opener; diff --git a/src/bibiman.rs b/src/bibiman.rs index 96a733c..1f19b24 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -692,12 +692,11 @@ impl Bibiman { None, )?; } - } - if self.popup_area.popup_list[popup_idx].0.contains("Note") { + } else if self.popup_area.popup_list[popup_idx].0.contains("Note") { let file = expand_home(&PathBuf::from(popup_entry.clone())); // let object: OsString = popup_entry.into(); if file.is_file() { - app::open_connected_file(cfg, &file.into_os_string())?; + app::open_connected_note(cfg, &file.into_os_string())?; self.close_popup(); } else { self.open_popup( diff --git a/tests/note-files/aristotle:poetics.txt b/tests/note-files/aristotle:poetics.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/note-files/aristotle:rhetoric.md b/tests/note-files/aristotle:rhetoric.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/note-files/bertram.txt b/tests/note-files/bertram.txt new file mode 100644 index 0000000..54d31e5 --- /dev/null +++ b/tests/note-files/bertram.txt @@ -0,0 +1 @@ +A simple text file with notes about this Betram dude diff --git a/tests/note-files/betram.txt b/tests/note-files/betram.txt deleted file mode 100644 index 54d31e5..0000000 --- a/tests/note-files/betram.txt +++ /dev/null @@ -1 +0,0 @@ -A simple text file with notes about this Betram dude diff --git a/tests/test-config.toml b/tests/test-config.toml index fd39b29..99d1d00 100644 --- a/tests/test-config.toml +++ b/tests/test-config.toml @@ -4,7 +4,7 @@ bibfiles = [ "tests/biblatex-test.bib" ] ## Default editor to use when editing files. Arguments are possible -# editor = "vim" # with args: "vim -y" +editor = "vim" # with args: "vim -y" ## Default app to open PDFs/Epubs # pdf_opener = "xdg-open" -- cgit v1.2.3 From b10615ade6bb2710cf6716f05cc496cb082d24ad Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 30 Jun 2025 10:43:38 +0200 Subject: opening notes in set editor --- src/app.rs | 16 +------ src/bibiman.rs | 47 +++++++++++++++++++-- tests/biblatex-test.bib | 12 +++--- tests/note-files/aristotle:poetics.txt | 0 tests/note-files/aristotle:rhetoric.md | 0 tests/note-files/aristotle_poetics.txt | 1 + tests/note-files/aristotle_rhetoric.md | 0 .../annotated-pdfs/ARIStotle:rheTORIC.PDF | Bin 25294 -> 0 bytes .../annotated-pdfs/ARIStotle_rheTORIC.PDF | Bin 0 -> 25294 bytes tests/pdf-files/aristotle:physics.pdf | Bin 25294 -> 0 bytes tests/pdf-files/aristotle:rhetoric.pdf | Bin 25294 -> 0 bytes tests/pdf-files/aristotle:rhetoric.txt | 0 tests/pdf-files/aristotle_physics.pdf | Bin 0 -> 25294 bytes tests/pdf-files/aristotle_rhetoric.pdf | Bin 0 -> 25294 bytes tests/pdf-files/aristotle_rhetoric.txt | 0 15 files changed, 53 insertions(+), 23 deletions(-) delete mode 100644 tests/note-files/aristotle:poetics.txt delete mode 100644 tests/note-files/aristotle:rhetoric.md create mode 100644 tests/note-files/aristotle_poetics.txt create mode 100644 tests/note-files/aristotle_rhetoric.md delete mode 100644 tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF create mode 100644 tests/pdf-files/annotated-pdfs/ARIStotle_rheTORIC.PDF delete mode 100644 tests/pdf-files/aristotle:physics.pdf delete mode 100644 tests/pdf-files/aristotle:rhetoric.pdf delete mode 100644 tests/pdf-files/aristotle:rhetoric.txt create mode 100644 tests/pdf-files/aristotle_physics.pdf create mode 100644 tests/pdf-files/aristotle_rhetoric.pdf create mode 100644 tests/pdf-files/aristotle_rhetoric.txt (limited to 'src/bibiman.rs') diff --git a/src/app.rs b/src/app.rs index f015494..496896a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,6 +18,7 @@ use crate::bibiman::CurrentArea; use crate::config::BibiConfig; use color_eyre::eyre::{Context, Ok, Result}; +use editor_command::EditorBuilder; // use super::Event; use crate::cliargs::CLIArgs; use crate::tui::commands::InputCmdAction; @@ -279,7 +280,7 @@ 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 { - self.bibiman.open_connected_res(cfg)?; + self.bibiman.open_connected_res(cfg, tui)?; } else if let Some(PopupKind::AppendToFile) = self.bibiman.popup_area.popup_kind { self.bibiman.append_entry_to_file(cfg)? @@ -423,19 +424,6 @@ pub fn open_connected_file(cfg: &BibiConfig, file: &OsStr) -> Result<()> { Ok(()) } -pub fn open_connected_note(cfg: &BibiConfig, file: &OsStr) -> Result<()> { - // let cmd = cfg.general.editor.as_ref().unwrap(); - - let _ = Command::new("xdg-open") - .arg(file) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .wrap_err("Opening file not possible"); - - Ok(()) -} - pub fn open_connected_link(cfg: &BibiConfig, link: &str) -> Result<()> { // Build command to execute pdf-reader. 'xdg-open' is Linux standard let cmd = &cfg.general.url_opener; diff --git a/src/bibiman.rs b/src/bibiman.rs index 1f19b24..e1e97ed 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -30,11 +30,12 @@ use crossterm::event::KeyCode; use editor_command::EditorBuilder; use ratatui::widgets::ScrollbarState; use regex::Regex; +use std::ffi::OsStr; use std::fs::{self, read_to_string}; use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::PathBuf; -use std::process::Command; +use std::process::{Command, Stdio}; use std::result::Result::Ok; use tui_input::Input; @@ -512,6 +513,46 @@ impl Bibiman { Ok(()) } + pub fn open_connected_note( + &mut self, + cfg: &BibiConfig, + tui: &mut Tui, + file: &OsStr, + ) -> Result<()> { + // get filecontent and citekey for calculating line number + let citekey: &str = &self.entry_table.entry_table_items + [self.entry_table.entry_table_state.selected().unwrap()] + .citekey + .clone(); + + // Exit TUI to enter editor + tui.exit()?; + // Use VISUAL or EDITOR. Set "vi" as last fallback + let mut cmd: Command = EditorBuilder::new() + .source(cfg.general.editor.as_ref()) + .environment() + .source(Some("vi")) + .build() + .unwrap(); + // Prepare arguments to open file at specific line + let status = cmd.arg(file).status()?; + if !status.success() { + eprintln!("Spawning editor failed with status {}", status); + } + + // Enter TUI again + tui.enter()?; + tui.terminal.clear()?; + + // Update the database and the lists to show changes + // Self::update_lists(self, cfg); + + // Select entry which was selected before entering editor + self.select_entry_by_citekey(citekey); + + Ok(()) + } + pub fn add_entry(&mut self) { if let CurrentArea::EntryArea = self.current_area { self.former_area = Some(FormerArea::EntryArea); @@ -662,7 +703,7 @@ impl Bibiman { Ok(()) } - pub fn open_connected_res(&mut self, cfg: &BibiConfig) -> Result<()> { + pub fn open_connected_res(&mut self, cfg: &BibiConfig, tui: &mut Tui) -> Result<()> { // Index of selected entry let entry_idx = self.entry_table.entry_table_state.selected().unwrap(); @@ -696,7 +737,7 @@ impl Bibiman { let file = expand_home(&PathBuf::from(popup_entry.clone())); // let object: OsString = popup_entry.into(); if file.is_file() { - app::open_connected_note(cfg, &file.into_os_string())?; + self.open_connected_note(cfg, tui, &file.into_os_string())?; self.close_popup(); } else { self.open_popup( diff --git a/tests/biblatex-test.bib b/tests/biblatex-test.bib index 4071dcb..692375e 100644 --- a/tests/biblatex-test.bib +++ b/tests/biblatex-test.bib @@ -9,7 +9,7 @@ model of particle physics.}, } -@collection{matuz:doody, +@collection{matuz_doody, title = {Contemporary Literary Criticism}, year = {1990}, location = {Detroit}, @@ -54,7 +54,7 @@ field}, } -@book{aristotle:anima, +@book{aristotle_anima, title = {De Anima}, author = {Aristotle}, location = {Cambridge}, @@ -68,7 +68,7 @@ editor}}, } -@book{aristotle:physics, +@book{aristotle_physics, title = {Physics}, shorttitle = {Physics}, author = {Aristotle}, @@ -84,7 +84,7 @@ annotation = {A \texttt{book} entry with a \texttt{translator} field}, } -@book{aristotle:poetics, +@book{aristotle_poetics, title = {Poetics}, shorttitle = {Poetics}, author = {Aristotle}, @@ -100,7 +100,7 @@ editor} as well as a \texttt{series} field}, } -@mvbook{aristotle:rhetoric, +@mvbook{aristotle_rhetoric, title = {The Rhetoric of {Aristotle} with a commentary by the late {Edward Meredith Cope}}, shorttitle = {Rhetoric}, @@ -441,7 +441,7 @@ @string{pup = {Princeton University Press}} -@incollection{westfahl:space, +@incollection{westfahl_space, title = {The True Frontier}, author = {Westfahl, Gary}, pages = {55--65}, diff --git a/tests/note-files/aristotle:poetics.txt b/tests/note-files/aristotle:poetics.txt deleted file mode 100644 index e69de29..0000000 diff --git a/tests/note-files/aristotle:rhetoric.md b/tests/note-files/aristotle:rhetoric.md deleted file mode 100644 index e69de29..0000000 diff --git a/tests/note-files/aristotle_poetics.txt b/tests/note-files/aristotle_poetics.txt new file mode 100644 index 0000000..a156c76 --- /dev/null +++ b/tests/note-files/aristotle_poetics.txt @@ -0,0 +1 @@ +Here some very boring information regarding Aristotle diff --git a/tests/note-files/aristotle_rhetoric.md b/tests/note-files/aristotle_rhetoric.md new file mode 100644 index 0000000..e69de29 diff --git a/tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF b/tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF deleted file mode 100644 index 6aaba88..0000000 Binary files a/tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF and /dev/null differ diff --git a/tests/pdf-files/annotated-pdfs/ARIStotle_rheTORIC.PDF b/tests/pdf-files/annotated-pdfs/ARIStotle_rheTORIC.PDF new file mode 100644 index 0000000..6aaba88 Binary files /dev/null and b/tests/pdf-files/annotated-pdfs/ARIStotle_rheTORIC.PDF differ diff --git a/tests/pdf-files/aristotle:physics.pdf b/tests/pdf-files/aristotle:physics.pdf deleted file mode 100644 index 6aaba88..0000000 Binary files a/tests/pdf-files/aristotle:physics.pdf and /dev/null differ diff --git a/tests/pdf-files/aristotle:rhetoric.pdf b/tests/pdf-files/aristotle:rhetoric.pdf deleted file mode 100644 index 6aaba88..0000000 Binary files a/tests/pdf-files/aristotle:rhetoric.pdf and /dev/null differ diff --git a/tests/pdf-files/aristotle:rhetoric.txt b/tests/pdf-files/aristotle:rhetoric.txt deleted file mode 100644 index e69de29..0000000 diff --git a/tests/pdf-files/aristotle_physics.pdf b/tests/pdf-files/aristotle_physics.pdf new file mode 100644 index 0000000..6aaba88 Binary files /dev/null and b/tests/pdf-files/aristotle_physics.pdf differ diff --git a/tests/pdf-files/aristotle_rhetoric.pdf b/tests/pdf-files/aristotle_rhetoric.pdf new file mode 100644 index 0000000..6aaba88 Binary files /dev/null and b/tests/pdf-files/aristotle_rhetoric.pdf differ diff --git a/tests/pdf-files/aristotle_rhetoric.txt b/tests/pdf-files/aristotle_rhetoric.txt new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3 From 3a40bbb367a79dc3660c12aa7f62e3efc378ea22 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 30 Jun 2025 16:33:01 +0200 Subject: update README: note feature --- README.md | 33 +++++++++++++++++++++++++-------- src/app.rs | 2 +- src/bibiman.rs | 32 ++++++++++++++++++++++++++++---- src/bibiman/entries.rs | 5 +++-- src/config.rs | 4 ++-- src/tui/ui.rs | 39 +++++++++++++++++++-------------------- 6 files changed, 78 insertions(+), 37 deletions(-) (limited to 'src/bibiman.rs') diff --git a/README.md b/README.md index 5bc40b3..4f0e505 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,16 @@ file_prefix = "/some/path/prefix" # of the format "citekey.pdf". Other PDF basenames are not accepted. # Use absolute paths (~ for HOME works). Otherwise, loading might not work. pdf_path = "/path/to/pdf-folder" +## Path to folder (with subfolders) containing note files with the basename of +## the format "citekey.extension". Other basenames are not accepted. The possible +## extensions can be set through the "note_extensions" array. +note_path = "path/to/note-files" +note_extensions = [ "md", "txt" ] + +## Symbols/chars to show if not has specific attachement + file_symbol = " " + link_symbol = "󰌹 " + note_symbol = "󰧮" ``` `bibfiles` @@ -246,13 +256,14 @@ pdf_path = "/path/to/pdf-folder" created through the `pdf_path` variable. Thus, it is safe to mix both approaches if wanted! -`pdf_path` +`pdf_path` and `note_path` -: The `pdf_path` is used as path wich is recursivley searched for files which - basename consists of the an entrys `citekey` plus a `.pdf` ending - (case-insensitive). Every file which matches this pattern for an existing - `citekey` is associated with the particular entry for the current `bibiman` - session and can be opened from within. +: The `pdf_path`/`note_path` is used as path wich is recursivley searched for + files which basename consists of the an entrys `citekey` plus a `.pdf` ending + or one of the specified note endinfs (case-insensitive). Every file which + matches this pattern for an existing `citekey` is associated with the + particular entry for the current `bibiman` session and can be opened from + within. ### Color Configuration @@ -272,6 +283,12 @@ warn_color = "124" bar_bg_color = "234" popup_bg_color = "234" selected_row_bg_color = "237" +note_color = "123" +file_color = "209" +link_color = "27" +author_color = "38" +title_color = "37" +year_color = "135" ``` Colors can be set through three different methods: @@ -309,7 +326,7 @@ These are the current features, the list will be updated: - [x] **Add Entry via DOI**. - [x] **Implement config file** for setting some default values like main bibfile, PDF-opener, or editor -- [ ] **Open related notes file** for specific entry. +- [x] **Open related notes file** for specific entry. - [ ] **Support Hayagriva(`.yaml`)** format as input (_on hold for now_, because the Hayagriva Yaml style doesn't offer keywords; s. issue in [Hayagriva repo](https://github.com/typst/hayagriva/issues/240)). @@ -346,7 +363,7 @@ Use the following keybindings to manage the TUI: There are some shortcuts to select an item from the opening/yanking popup without navigating the list: -- `o-o`|`o-l`: directly opens the first file/link for the selected entry. +- `o-o`|`o-l`|`o-n`: directly opens the first file|link|note for the selected entry. - `y-y`: directly yanks the citekey of the selected entry to the clipboard. ## Search diff --git a/src/app.rs b/src/app.rs index 496896a..18a97e6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -83,7 +83,7 @@ impl App { } else if let Some(PopupKind::YankItem) | Some(PopupKind::OpenRes) = self.bibiman.popup_area.popup_kind { - self.bibiman.fast_selection(cfg, key_event.code)?; + self.bibiman.fast_selection(cfg, &mut tui, key_event.code)?; } let command = if self.input_mode { CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) diff --git a/src/bibiman.rs b/src/bibiman.rs index e1e97ed..9cc9280 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -367,9 +367,9 @@ impl Bibiman { .entry_table_state .selected_column() .unwrap() - == 3 + == 4 { - self.entry_table.entry_table_state.select_first_column(); + self.entry_table.entry_table_state.select_column(Some(1)); } else { self.entry_table.entry_table_state.select_next_column(); } @@ -396,7 +396,7 @@ impl Bibiman { .entry_table_state .selected_column() .unwrap() - == 0 + == 1 { self.entry_table.entry_table_state.select_last_column(); } else { @@ -788,11 +788,17 @@ impl Bibiman { /// /// `o` -> opens the first file of the `filepath` `Vec` for the current entry /// `l` -> opens the link of the current entry + /// `n` -> opens the first note /// /// **Yanking popup** /// /// `y` -> yanks the citekey for the current entry - pub fn fast_selection(&mut self, cfg: &BibiConfig, key_code: KeyCode) -> Result<()> { + pub fn fast_selection( + &mut self, + cfg: &BibiConfig, + tui: &mut Tui, + key_code: KeyCode, + ) -> Result<()> { if let CurrentArea::PopupArea = self.current_area { let entry_idx = self.entry_table.entry_table_state.selected().unwrap(); match self.popup_area.popup_kind { @@ -817,6 +823,24 @@ impl Bibiman { } } } + KeyCode::Char('n') => { + let file = self.entry_table.entry_table_items[entry_idx].notes.clone(); + if file.is_some() { + let file = expand_home(&PathBuf::from(file.unwrap()[0].clone())); + // let object: OsString = popup_entry.into(); + if file.is_file() { + self.open_connected_note(cfg, tui, &file.into_os_string())?; + self.close_popup(); + } else { + self.open_popup( + PopupKind::MessageError, + Some("No valid file path: "), + Some(file.to_str().unwrap()), + None, + )?; + } + } + } KeyCode::Char('l') => { if self.entry_table.entry_table_items[entry_idx] .doi_url diff --git a/src/bibiman/entries.rs b/src/bibiman/entries.rs index e0c230b..9b536fd 100644 --- a/src/bibiman/entries.rs +++ b/src/bibiman/entries.rs @@ -47,8 +47,9 @@ impl EntryTable { // entry_table let entry_table_state = TableState::default() .with_selected(0) - .with_selected_column(0) - .with_selected_cell(Some((0, 0))); + .with_selected_column(1) + // other two values above are ignored, if selected cell isn't fitting + .with_selected_cell(Some((0, 1))); let entry_scroll_state = ScrollbarState::new(entry_table_items.len()); let entry_info_scroll_state = ScrollbarState::default(); Self { diff --git a/src/config.rs b/src/config.rs index f67cb9d..278f4b1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -85,7 +85,7 @@ const DEFAULT_CONFIG: &str = r##" # selected_row_bg_color = "237" # note_color = "123" # file_color = "209" -# link_color = "27" +# link_color = "39" # author_color = "38" # title_color = "37" # year_color = "135" @@ -215,7 +215,7 @@ impl BibiConfig { selected_row_bg_color: Color::Indexed(237), note_color: Color::Indexed(123), file_color: Color::Indexed(209), - link_color: Color::Indexed(33), + link_color: Color::Indexed(39), author_color: Color::Indexed(38), title_color: Color::Indexed(37), year_color: Color::Indexed(135), diff --git a/src/tui/ui.rs b/src/tui/ui.rs index ac44d09..3c83935 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -580,7 +580,7 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec .bg(cfg.colors.bar_bg_color); let header = Row::new(vec![ - // Cell::from(Line::from("")).bg(cfg.colors.bar_bg_color), + Cell::from(Line::from("")).bg(cfg.colors.bar_bg_color), Cell::from( Line::from(vec![{ Span::raw("Author") }, { if let Some(EntryTableColumn::Authors) = @@ -704,21 +704,30 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec let mut symbol_vec = vec![]; if let Some(f) = &item.symbols[0] { - symbol_vec.push(Span::styled(f, Style::new().fg(cfg.colors.file_color))); + symbol_vec.push(Span::styled( + f, + Style::new().fg(cfg.colors.file_color).bold(), + )); } if let Some(l) = &item.symbols[1] { - symbol_vec.push(Span::styled(l, Style::new().fg(cfg.colors.link_color))); + symbol_vec.push(Span::styled( + l, + Style::new().fg(cfg.colors.link_color).bold(), + )); } if let Some(n) = &item.symbols[2] { - symbol_vec.push(Span::styled(n, Style::new().fg(cfg.colors.note_color))) + symbol_vec.push(Span::styled( + n, + Style::new().fg(cfg.colors.note_color).bold(), + )) } let row = Row::new(vec![ + Cell::from(Line::from(symbol_vec)), Cell::from(Line::from(item.authors)), Cell::from(Line::from(item.title)), Cell::from(Line::from(item.year)), Cell::from(Line::from(item.pubtype)), - Cell::from(Line::from(symbol_vec)), ]); // let row = item @@ -738,6 +747,11 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec let entry_table = Table::new( rows, [ + Constraint::Length( + (cfg.general.file_symbol.chars().count() + + cfg.general.link_symbol.chars().count() + + cfg.general.note_symbol.chars().count()) as u16, + ), Constraint::Percentage(20), Constraint::Fill(1), Constraint::Length( @@ -750,12 +764,6 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec }, ), Constraint::Percentage(10), - Constraint::Length( - (cfg.general.file_symbol.chars().count() - + cfg.general.link_symbol.chars().count() - + cfg.general.note_symbol.chars().count() - + 1) as u16, - ), ], ) .block(block) @@ -917,15 +925,6 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, ), ])); } - // if cur_entry.filepath.is_some() { - // lines.push(Line::from(vec![ - // Span::styled("File: ", style_value), - // Span::styled( - // cur_entry.filepath().to_string_lossy(), - // Style::new().fg(cfg.colors.main_text_color), - // ), - // ])); - // } lines.push(Line::from("")); lines.push(Line::from(vec![Span::styled( cur_entry.abstract_text.clone(), -- cgit v1.2.3 From 9bd2f6fef0d835ffb97e18993161e6639c98d2d1 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Mon, 30 Jun 2025 21:49:53 +0200 Subject: align resource symbols, update README --- README.md | 2 ++ src/app.rs | 13 +++++++++---- src/bibiman.rs | 2 +- src/tui/ui.rs | 19 +++++++++++++++++-- tests/test-config.toml | 6 +++--- 5 files changed, 32 insertions(+), 10 deletions(-) (limited to 'src/bibiman.rs') diff --git a/README.md b/README.md index 4f0e505..ed620e8 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Here's a small impression how it looks and works: [![bibiman.gif](https://i.postimg.cc/Y0mCNDMg/bibiman.gif)](https://postimg.cc/ct0W0mK4) +![screenshot with new note feature](https://codeberg.org/attachments/69d35f36-cff3-43e5-8bfd-361064ba8ab2) + ## Installation ### Crates.io diff --git a/src/app.rs b/src/app.rs index 18a97e6..f912614 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,15 +16,14 @@ ///// use crate::bibiman::CurrentArea; -use crate::config::BibiConfig; -use color_eyre::eyre::{Context, Ok, Result}; -use editor_command::EditorBuilder; -// use super::Event; use crate::cliargs::CLIArgs; +use crate::config::BibiConfig; use crate::tui::commands::InputCmdAction; use crate::tui::popup::PopupKind; use crate::tui::{self, Tui}; use crate::{bibiman::Bibiman, tui::commands::CmdAction}; +use color_eyre::eyre::{Context, Ok, Result}; +use crossterm::event::KeyCode; use std::ffi::OsStr; use std::path::PathBuf; use std::process::{Command, Stdio}; @@ -84,6 +83,12 @@ impl App { self.bibiman.popup_area.popup_kind { self.bibiman.fast_selection(cfg, &mut tui, key_event.code)?; + // if a fast match char was used, restart event-loop. + // otherwise, the fast match char will be executed as command + match key_event.code { + KeyCode::Char('o' | 'l' | 'n' | 'y') => continue, + _ => {} + } } let command = if self.input_mode { CmdAction::Input(InputCmdAction::parse(key_event, &self.input)) diff --git a/src/bibiman.rs b/src/bibiman.rs index 9cc9280..f95bbc0 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -35,7 +35,7 @@ use std::fs::{self, read_to_string}; use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::PathBuf; -use std::process::{Command, Stdio}; +use std::process::Command; use std::result::Result::Ok; use tui_input::Input; diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 3c83935..be53f61 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -300,7 +300,7 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { }; let bottom_info = if let Some(PopupKind::OpenRes) = app.bibiman.popup_area.popup_kind { - " (j,k|↓,↑) ━ (o,l) ━ (ENTER) ━ (ESC) ".bold() + " (j,k|↓,↑) ━ (o,l,n) ━ (ENTER) ━ (ESC) ".bold() } else if let Some(PopupKind::YankItem) = app.bibiman.popup_area.popup_kind { " (j,k|↓,↑) ━ (y) ━ (ENTER) ━ (ESC) ".bold() } else { @@ -580,7 +580,7 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec .bg(cfg.colors.bar_bg_color); let header = Row::new(vec![ - Cell::from(Line::from("")).bg(cfg.colors.bar_bg_color), + Cell::from(Line::from("Res.")).bg(cfg.colors.bar_bg_color), Cell::from( Line::from(vec![{ Span::raw("Author") }, { if let Some(EntryTableColumn::Authors) = @@ -703,23 +703,38 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec let mut symbol_vec = vec![]; + // use default or custom symbols for resources + // if an entry has no, replace it with the correct number + // of whitespace to align the symbols correct if let Some(f) = &item.symbols[0] { symbol_vec.push(Span::styled( f, Style::new().fg(cfg.colors.file_color).bold(), )); + } else { + symbol_vec.push(Span::raw( + " ".repeat(cfg.general.file_symbol.chars().count()), + )); } if let Some(l) = &item.symbols[1] { symbol_vec.push(Span::styled( l, Style::new().fg(cfg.colors.link_color).bold(), )); + } else { + symbol_vec.push(Span::raw( + " ".repeat(cfg.general.link_symbol.chars().count()), + )); } if let Some(n) = &item.symbols[2] { symbol_vec.push(Span::styled( n, Style::new().fg(cfg.colors.note_color).bold(), )) + } else { + symbol_vec.push(Span::raw( + " ".repeat(cfg.general.note_symbol.chars().count()), + )); } let row = Row::new(vec![ diff --git a/tests/test-config.toml b/tests/test-config.toml index 51bd4e6..1d29043 100644 --- a/tests/test-config.toml +++ b/tests/test-config.toml @@ -28,9 +28,9 @@ note_path = "tests/note-files" note_extensions = [ "md", "txt" ] ## Symbols/chars to show if not has specific attachement - file_symbol = " " - link_symbol = "󰌹 " - note_symbol = "󰧮" +file_symbol = " " +link_symbol = "󰌹 " +note_symbol = "󰧮" # [colors] ## Default values for dark-themed terminal -- cgit v1.2.3 From 61b22382e0979f756538215047bafd30866ccf1e Mon Sep 17 00:00:00 2001 From: lukeflo Date: Thu, 3 Jul 2025 15:06:19 +0200 Subject: detach opened note from terminal window running bibiman --- src/bibiman.rs | 96 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 28 deletions(-) (limited to 'src/bibiman.rs') diff --git a/src/bibiman.rs b/src/bibiman.rs index f95bbc0..200db96 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -25,7 +25,7 @@ use crate::tui::Tui; use crate::{app, cliargs}; use crate::{bibiman::entries::EntryTable, bibiman::keywords::TagList}; use arboard::Clipboard; -use color_eyre::eyre::{Error, Result}; +use color_eyre::eyre::{Context, Error, Result}; use crossterm::event::KeyCode; use editor_command::EditorBuilder; use ratatui::widgets::ScrollbarState; @@ -35,7 +35,7 @@ use std::fs::{self, read_to_string}; use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::PathBuf; -use std::process::Command; +use std::process::{Command, Stdio}; use std::result::Result::Ok; use tui_input::Input; @@ -520,35 +520,63 @@ impl Bibiman { file: &OsStr, ) -> Result<()> { // get filecontent and citekey for calculating line number - let citekey: &str = &self.entry_table.entry_table_items - [self.entry_table.entry_table_state.selected().unwrap()] - .citekey - .clone(); - // Exit TUI to enter editor - tui.exit()?; - // Use VISUAL or EDITOR. Set "vi" as last fallback - let mut cmd: Command = EditorBuilder::new() - .source(cfg.general.editor.as_ref()) - .environment() - .source(Some("vi")) - .build() - .unwrap(); - // Prepare arguments to open file at specific line - let status = cmd.arg(file).status()?; - if !status.success() { - eprintln!("Spawning editor failed with status {}", status); - } + match std::env::var("TERM") { + Ok(sh) => { + let editor = if let Some(e) = cfg.general.editor.clone() { + e + } else if let Ok(e) = std::env::var("VISUAL") { + e + } else if let Ok(e) = std::env::var("EDITOR") { + e + } else { + String::from("vi") + }; + let _ = Command::new(sh) + .arg("-e") + .arg(editor) + .arg(file) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .wrap_err("Couldn't run editor"); + // Prepare arguments to open file at specific line + // let status = note_cmd.status()?; + // if !status.success() { + // eprintln!("Spawning editor failed with status {}", status); + // } + } + Err(_e) => { + let citekey: &str = &self.entry_table.entry_table_items + [self.entry_table.entry_table_state.selected().unwrap()] + .citekey + .clone(); + // Exit TUI to enter editor + tui.exit()?; + // Use VISUAL or EDITOR. Set "vi" as last fallback + let mut note_cmd: Command = EditorBuilder::new() + .source(cfg.general.editor.clone()) + .environment() + .source(Some("vi")) + .build() + .unwrap(); + // Prepare arguments to open file at specific line + let status = note_cmd.arg(file).status()?; + if !status.success() { + eprintln!("Spawning editor failed with status {}", status); + } - // Enter TUI again - tui.enter()?; - tui.terminal.clear()?; + // Enter TUI again + tui.enter()?; + tui.terminal.clear()?; - // Update the database and the lists to show changes - // Self::update_lists(self, cfg); + // Update the database and the lists to show changes + // Self::update_lists(self, cfg); - // Select entry which was selected before entering editor - self.select_entry_by_citekey(citekey); + // Select entry which was selected before entering editor + self.select_entry_by_citekey(citekey); + } + } Ok(()) } @@ -808,7 +836,19 @@ impl Bibiman { .filepath .clone(); if file.is_some() { - let file = expand_home(&PathBuf::from(file.unwrap()[0].clone())); + let file = if self.entry_table.entry_table_items[entry_idx].file_field + && cfg.general.file_prefix.is_some() + { + cfg.general + .file_prefix + .clone() + .unwrap() + .join(&file.unwrap()[0]) + .into_os_string() + } else { + file.unwrap()[0].clone() + }; + let file = expand_home(&PathBuf::from(file)); // let object: OsString = popup_entry.into(); if file.is_file() { app::open_connected_file(cfg, &file.into_os_string())?; -- cgit v1.2.3 From 2594cf34dcf2f04f398dab7b6ecae364eb4c7d17 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Fri, 4 Jul 2025 13:56:55 +0200 Subject: impl `PopupItem` enum, adapt ui: include colors --- src/app.rs | 30 ++++++++++++++++++++++++------ src/bibiman.rs | 17 +++++++++++++---- src/tui/popup.rs | 16 +++++++++++++--- src/tui/ui.rs | 16 ++++++++++++---- 4 files changed, 62 insertions(+), 17 deletions(-) (limited to 'src/bibiman.rs') diff --git a/src/app.rs b/src/app.rs index f912614..d645dbe 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,7 +19,7 @@ use crate::bibiman::CurrentArea; use crate::cliargs::CLIArgs; use crate::config::BibiConfig; use crate::tui::commands::InputCmdAction; -use crate::tui::popup::PopupKind; +use crate::tui::popup::{PopupItem, PopupKind}; use crate::tui::{self, Tui}; use crate::{bibiman::Bibiman, tui::commands::CmdAction}; use color_eyre::eyre::{Context, Ok, Result}; @@ -311,13 +311,25 @@ impl App { .selected() .unwrap(); let entry = self.bibiman.entry_table.entry_table_items[idx].clone(); - let mut items = vec![("Citekey: ".to_string(), entry.citekey.clone())]; + let mut items = vec![( + "Citekey: ".to_string(), + entry.citekey.clone(), + PopupItem::Default, + )]; if entry.doi_url.is_some() { - items.push(("Weblink: ".into(), entry.doi_url.unwrap().clone())) + items.push(( + "Weblink: ".into(), + entry.doi_url.unwrap().clone(), + PopupItem::Link, + )) } if entry.filepath.is_some() { entry.filepath.unwrap().iter().for_each(|p| { - items.push(("Filepath: ".into(), p.clone().into_string().unwrap())) + items.push(( + "Filepath: ".into(), + p.clone().into_string().unwrap(), + PopupItem::Entryfile, + )) }); // items.push(( // "Filepath: ".into(), @@ -348,13 +360,14 @@ impl App { .selected() .unwrap(); let entry = self.bibiman.entry_table.entry_table_items[idx].clone(); - let mut items: Vec<(String, String)> = vec![]; + let mut items: Vec<(String, String, PopupItem)> = vec![]; if entry.filepath.is_some() || entry.doi_url.is_some() || entry.notes.is_some() { if entry.doi_url.is_some() { items.push(( "Weblink (DOI/URL): ".into(), entry.doi_url.unwrap().clone(), + PopupItem::Link, )) } if entry.filepath.is_some() { @@ -374,12 +387,17 @@ impl App { } else { p.clone().into_string().unwrap() }, + PopupItem::Entryfile, )) }); } if entry.notes.is_some() { entry.notes.unwrap().iter().for_each(|n| { - items.push(("Note: ".into(), n.clone().into_string().unwrap())); + items.push(( + "Note: ".into(), + n.clone().into_string().unwrap(), + PopupItem::Notefile, + )); }); } diff --git a/src/bibiman.rs b/src/bibiman.rs index 200db96..fb72e93 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -20,7 +20,7 @@ use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::{bibisetup::*, search::BibiSearch}; use crate::cliargs::CLIArgs; use crate::config::BibiConfig; -use crate::tui::popup::{PopupArea, PopupKind}; +use crate::tui::popup::{PopupArea, PopupItem, PopupKind}; use crate::tui::Tui; use crate::{app, cliargs}; use crate::{bibiman::entries::EntryTable, bibiman::keywords::TagList}; @@ -161,7 +161,7 @@ impl Bibiman { popup_kind: PopupKind, message: Option<&str>, object: Option<&str>, - items: Option>, + items: Option>, ) -> Result<()> { if let CurrentArea::EntryArea = self.current_area { self.former_area = Some(FormerArea::EntryArea); @@ -631,10 +631,18 @@ impl Bibiman { } pub fn append_to_file(&mut self) { - let mut items = vec![("Create new file".to_owned(), "".to_string())]; + let mut items = vec![( + "Create new file".to_owned(), + "".to_string(), + PopupItem::Default, + )]; if self.main_bibfiles.len() > 1 { for f in self.main_bibfiles.clone() { - items.push(("File: ".into(), f.to_str().unwrap().to_owned())); + items.push(( + "File: ".into(), + f.to_str().unwrap().to_owned(), + PopupItem::Bibfile, + )); } } else { items.push(( @@ -645,6 +653,7 @@ impl Bibiman { .to_str() .unwrap() .to_owned(), + PopupItem::Bibfile, )); } self.popup_area.popup_selection(items); diff --git a/src/tui/popup.rs b/src/tui/popup.rs index 93b01c3..da44744 100644 --- a/src/tui/popup.rs +++ b/src/tui/popup.rs @@ -40,13 +40,23 @@ pub enum PopupKind { YankItem, } +#[derive(Debug)] +pub enum PopupItem { + Bibfile, + Entryfile, + Notefile, + Link, + Default, + None, +} + #[derive(Debug, Default)] pub struct PopupArea { pub is_popup: bool, pub popup_kind: Option, pub popup_message: String, pub popup_scroll_pos: u16, - pub popup_list: Vec<(String, String)>, + pub popup_list: Vec<(String, String, PopupItem)>, pub popup_state: ListState, pub popup_sel_item: String, // pub add_entry_input: String, @@ -116,8 +126,8 @@ impl PopupArea { /// Opens a popup with a selectable list /// - /// The list items are passed as argument of the kind `Vec<(String, String)>`. - pub fn popup_selection(&mut self, items: Vec<(String, String)>) { + /// The list items are passed as argument of the kind `Vec<(String, String, PopupItem)>`. + pub fn popup_selection(&mut self, items: Vec<(String, String, PopupItem)>) { self.popup_list = items; // self.popup_kind = Some(PopupKind::SelectRes); self.is_popup = true; diff --git a/src/tui/ui.rs b/src/tui/ui.rs index be53f61..2c30154 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -17,7 +17,7 @@ use std::path::PathBuf; -use super::popup::PopupArea; +use super::popup::{PopupArea, PopupItem}; use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::{CurrentArea, FormerArea}; use crate::cliargs::CLIArgs; @@ -280,9 +280,17 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { .popup_list .iter() .map( - |(mes, obj)| { + |(mes, obj, i)| { + let style = match i { + PopupItem::Bibfile => Style::new().fg(cfg.colors.entry_color), + PopupItem::Entryfile => Style::new().fg(cfg.colors.file_color), + PopupItem::Notefile => Style::new().fg(cfg.colors.note_color), + PopupItem::Link => Style::new().fg(cfg.colors.link_color), + PopupItem::Default => Style::new(), + PopupItem::None => Style::new(), + }; ListItem::from(Line::from(vec![ - Span::styled(mes, Style::new().bold()), + Span::styled(mes, style.bold()), Span::raw(obj), ])) }, // ListItem::from(mes.to_owned() + obj) @@ -333,7 +341,7 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { .popup_area .popup_list .iter() - .max_by(|(mes, obj), (m, o)| { + .max_by(|(mes, obj, _ik), (m, o, _i)| { let x = mes.chars().count() + obj.chars().count(); let y = m.chars().count() + o.chars().count(); x.cmp(&y) -- cgit v1.2.3 From 52079ee745831b06a6c4060f38ee49e42d689dcd Mon Sep 17 00:00:00 2001 From: lukeflo Date: Fri, 4 Jul 2025 15:05:08 +0200 Subject: use `PopupItem` to determine resource type --- src/bibiman.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/bibiman.rs') diff --git a/src/bibiman.rs b/src/bibiman.rs index fb72e93..6aec1fb 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -749,12 +749,12 @@ impl Bibiman { let popup_entry = self.popup_area.popup_list[popup_idx].1.clone(); // Choose ressource depending an selected popup field - if self.popup_area.popup_list[popup_idx].0.contains("Weblink") { + if let PopupItem::Link = self.popup_area.popup_list[popup_idx].2 { let object = self.entry_table.entry_table_items[entry_idx].doi_url(); let url = app::prepare_weblink(object); app::open_connected_link(cfg, &url)?; self.close_popup(); - } else if self.popup_area.popup_list[popup_idx].0.contains("File") { + } else if let PopupItem::Entryfile = self.popup_area.popup_list[popup_idx].2 { // TODO: Selection for multiple files // let object = self.entry_table.entry_table_items[entry_idx].filepath()[0]; let file = expand_home(&PathBuf::from(popup_entry.clone())); @@ -770,7 +770,7 @@ impl Bibiman { None, )?; } - } else if self.popup_area.popup_list[popup_idx].0.contains("Note") { + } else if let PopupItem::Notefile = self.popup_area.popup_list[popup_idx].2 { let file = expand_home(&PathBuf::from(popup_entry.clone())); // let object: OsString = popup_entry.into(); if file.is_file() { -- cgit v1.2.3 From 80c04702012cb1a43711d559e8ffbe9e250b1a57 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sat, 5 Jul 2025 20:21:52 +0200 Subject: first steps for create new note --- src/app.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/bibiman.rs | 4 ++++ src/tui/commands.rs | 2 ++ src/tui/popup.rs | 2 ++ 4 files changed, 67 insertions(+), 3 deletions(-) (limited to 'src/bibiman.rs') diff --git a/src/app.rs b/src/app.rs index d475328..14cc864 100644 --- a/src/app.rs +++ b/src/app.rs @@ -365,7 +365,7 @@ impl App { { if entry.doi_url.is_some() { items.push(( - format!("{} ", cfg.general.link_symbol.clone().trim()), + "Link: ".into(), entry.doi_url.unwrap().clone(), PopupItem::Link, )) @@ -373,7 +373,7 @@ impl App { if entry.filepath.is_some() { entry.filepath.unwrap().iter().for_each(|p| { items.push(( - format!("{} ", cfg.general.file_symbol.clone().trim()), + "File: ".into(), // p.clone().into_string().unwrap(), if entry.file_field && cfg.general.file_prefix.is_some() { cfg.general @@ -394,7 +394,7 @@ impl App { if entry.notes.is_some() { entry.notes.unwrap().iter().for_each(|n| { items.push(( - format!("{} ", cfg.general.note_symbol.clone().trim()), + "Note: ".into(), n.clone().into_string().unwrap(), PopupItem::Notefile, )); @@ -419,6 +419,62 @@ impl App { self.bibiman.add_entry(); } } + CmdAction::CreateNote => { + if let CurrentArea::EntryArea = self.bibiman.current_area { + if cfg.general.note_path.is_some() + && cfg.general.note_extensions.is_some() + && self.bibiman.entry_table.entry_table_items[self + .bibiman + .entry_table + .entry_table_state + .selected() + .unwrap()] + .notes + .is_none() + { + let mut items = vec![]; + for ex in cfg.general.note_extensions.as_ref().unwrap() { + items.push(( + self.bibiman.entry_table.entry_table_items[self + .bibiman + .entry_table + .entry_table_state + .selected() + .unwrap()] + .citekey() + .to_string(), + ex.clone(), + PopupItem::Notefile, + )); + } + self.bibiman + .open_popup(PopupKind::CreateNote, None, None, Some(items)); + } else if cfg.general.note_path.is_some() + && self.bibiman.entry_table.entry_table_items[self + .bibiman + .entry_table + .entry_table_state + .selected() + .unwrap()] + .notes + .is_some() + { + self.bibiman.open_popup( + PopupKind::MessageError, + Some("Selected entry already has a connected note"), + None, + None, + )?; + } else { + self.bibiman.open_popup( + PopupKind::MessageError, + Some("No note path found. Set it in config file."), + None, + None, + )?; + } + } + } CmdAction::ShowHelp => { self.bibiman.open_popup(PopupKind::Help, None, None, None)?; } diff --git a/src/bibiman.rs b/src/bibiman.rs index 6aec1fb..c92b869 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -740,6 +740,10 @@ impl Bibiman { Ok(()) } + pub fn create_note(&mut self, cfg: &BibiConfig) -> Result<()> { + Ok(()) + } + pub fn open_connected_res(&mut self, cfg: &BibiConfig, tui: &mut Tui) -> Result<()> { // Index of selected entry let entry_idx = self.entry_table.entry_table_state.selected().unwrap(); diff --git a/src/tui/commands.rs b/src/tui/commands.rs index 08ee677..47d2802 100644 --- a/src/tui/commands.rs +++ b/src/tui/commands.rs @@ -73,6 +73,8 @@ pub enum CmdAction { ShowHelp, // Add new entry AddEntry, + // Create note + CreateNote, // Do nothing. Nothing, } diff --git a/src/tui/popup.rs b/src/tui/popup.rs index 4aaa2c1..46e4792 100644 --- a/src/tui/popup.rs +++ b/src/tui/popup.rs @@ -38,6 +38,8 @@ pub enum PopupKind { AddEntry, /// select an item of the current entry to yank to clipboard YankItem, + /// Create a new note, select extension + CreateNote, } #[derive(Debug)] -- cgit v1.2.3 From 2990df627ff54f01bafdcab767c0a73198e9e6cc Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sat, 5 Jul 2025 22:29:18 +0200 Subject: create note function impl --- src/app.rs | 10 ++++- src/bibiman.rs | 33 +++++++++++++++++ src/tui/commands.rs | 2 + src/tui/ui.rs | 104 ++++++++++++++++++++++++++++++++++------------------ 4 files changed, 111 insertions(+), 38 deletions(-) (limited to 'src/bibiman.rs') diff --git a/src/app.rs b/src/app.rs index 14cc864..01424bc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -192,7 +192,8 @@ impl App { } Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) - | Some(PopupKind::YankItem) => { + | Some(PopupKind::YankItem) + | Some(PopupKind::CreateNote) => { self.bibiman.popup_area.popup_state.scroll_down_by(1) } _ => {} @@ -213,7 +214,8 @@ impl App { } Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) - | Some(PopupKind::YankItem) => { + | Some(PopupKind::YankItem) + | Some(PopupKind::CreateNote) => { self.bibiman.popup_area.popup_state.scroll_up_by(1) } _ => {} @@ -273,6 +275,8 @@ impl App { self.bibiman.close_popup(); } else if let Some(PopupKind::YankItem) = self.bibiman.popup_area.popup_kind { self.bibiman.close_popup(); + } else if let Some(PopupKind::CreateNote) = self.bibiman.popup_area.popup_kind { + self.bibiman.close_popup(); } } else { self.bibiman.reset_current_list(); @@ -291,6 +295,8 @@ impl App { self.bibiman.append_entry_to_file(cfg)? } else if let Some(PopupKind::YankItem) = self.bibiman.popup_area.popup_kind { self.bibiman.yank_entry_field()? + } else if let Some(PopupKind::CreateNote) = self.bibiman.popup_area.popup_kind { + self.bibiman.create_note(cfg)? } } } diff --git a/src/bibiman.rs b/src/bibiman.rs index c92b869..71ac831 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -238,6 +238,18 @@ impl Bibiman { )) } } + PopupKind::CreateNote => { + if items.is_some() { + self.popup_area.popup_kind = Some(PopupKind::CreateNote); + self.popup_area.popup_selection(items.unwrap()); + self.popup_area.popup_state.select(Some(0)); + Ok(()) + } else { + Err(Error::msg( + "No Vec<(String, String)> passed as argument to generate the items list", + )) + } + } } } @@ -741,6 +753,27 @@ impl Bibiman { } pub fn create_note(&mut self, cfg: &BibiConfig) -> Result<()> { + // Index of selected entry + let entry_idx = self.entry_table.entry_table_state.selected().unwrap(); + let citekey = self.entry_table.entry_table_items[entry_idx].citekey(); + + // Index of selected popup field + let popup_idx = self.popup_area.popup_state.selected().unwrap(); + let ext = self.popup_area.popup_list[popup_idx].1.clone(); + + let basename = PathBuf::from(citekey).with_extension(ext); + let path = cfg.general.note_path.as_ref().unwrap(); + + let new_file = path.join(basename); + + let new_file = if new_file.starts_with("~") { + expand_home(&new_file) + } else { + new_file + }; + + File::create_new(new_file).unwrap(); + self.close_popup(); Ok(()) } diff --git a/src/tui/commands.rs b/src/tui/commands.rs index 47d2802..89fcf44 100644 --- a/src/tui/commands.rs +++ b/src/tui/commands.rs @@ -151,6 +151,8 @@ impl From for CmdAction { // Open linked ressource KeyCode::Char('o') => Self::Open, // KeyCode::Char('u') => Self::Open(OpenRessource::WebLink), + // Create note file + KeyCode::Char('n') => Self::CreateNote, // Edit currently selected entry KeyCode::Char('e') => Self::EditFile, // Yank selected item/value diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 0a34e51..69ca058 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -273,33 +273,49 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { frame.render_widget(Clear, popup_area); frame.render_widget(&content, popup_area) } - Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) | Some(PopupKind::YankItem) => { - let list_items: Vec = app - .bibiman - .popup_area - .popup_list - .iter() - .map( - |(mes, obj, i)| { - let style: Color = match i { - PopupItem::Bibfile => cfg.colors.entry_color, - PopupItem::Citekey => cfg.colors.entry_color, - PopupItem::Entryfile => cfg.colors.file_color, - PopupItem::Notefile => cfg.colors.note_color, - PopupItem::Link => cfg.colors.link_color, - PopupItem::Default => cfg.colors.main_text_color, - PopupItem::None => cfg.colors.main_text_color, - }; - ListItem::from( - Line::from(vec![ - Span::styled(mes, Style::new().bold()), - Span::raw(obj), - ]) - .fg(style), - ) - }, // ListItem::from(mes.to_owned() + obj) - ) - .collect(); + Some(PopupKind::OpenRes) + | Some(PopupKind::AppendToFile) + | Some(PopupKind::YankItem) + | Some(PopupKind::CreateNote) => { + let list_items: Vec = if let Some(PopupKind::CreateNote) = + app.bibiman.popup_area.popup_kind + { + app.bibiman + .popup_area + .popup_list + .iter() + .map(|(m, o, _i)| { + ListItem::from(Line::from(vec![Span::raw(m), Span::raw("."), Span::raw(o)])) + .fg(cfg.colors.note_color) + }) + .collect() + } else { + app.bibiman + .popup_area + .popup_list + .iter() + .map( + |(mes, obj, i)| { + let style: Color = match i { + PopupItem::Bibfile => cfg.colors.entry_color, + PopupItem::Citekey => cfg.colors.entry_color, + PopupItem::Entryfile => cfg.colors.file_color, + PopupItem::Notefile => cfg.colors.note_color, + PopupItem::Link => cfg.colors.link_color, + PopupItem::Default => cfg.colors.main_text_color, + PopupItem::None => cfg.colors.main_text_color, + }; + ListItem::from( + Line::from(vec![ + Span::styled(mes, Style::new().bold()), + Span::raw(obj), + ]) + .fg(style), + ) + }, // ListItem::from(mes.to_owned() + obj) + ) + .collect() + }; let title = if let Some(PopupKind::OpenRes) = app.bibiman.popup_area.popup_kind { " Open " @@ -307,21 +323,23 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { " Select file to append entry " } else if let Some(PopupKind::YankItem) = app.bibiman.popup_area.popup_kind { " Yank to clipboard " + } else if let Some(PopupKind::CreateNote) = app.bibiman.popup_area.popup_kind { + " Create Note with extension " } else { " Select " }; let bottom_info = if let Some(PopupKind::OpenRes) = app.bibiman.popup_area.popup_kind { - " (j,k|↓,↑) ━ (o,l,n) ━ (ENTER) ━ (ESC) ".bold() + " (j,k|↓,↑) ━ (o,l,n) ━ (ENTER) ━ (ESC) " } else if let Some(PopupKind::YankItem) = app.bibiman.popup_area.popup_kind { - " (j,k|↓,↑) ━ (y) ━ (ENTER) ━ (ESC) ".bold() + " (j,k|↓,↑) ━ (y) ━ (ENTER) ━ (ESC) " } else { - " (j,k|↓,↑) ━ (ENTER) ━ (ESC) ".bold() + " (j,k|↓,↑) ━ (ENTER) ━ (ESC) " }; let block = Block::bordered() .title_top(title.bold()) - .title_bottom(bottom_info) + .title_bottom(bottom_info.bold()) .title_alignment(Alignment::Center) .style( Style::new() @@ -356,15 +374,29 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) { // Now take the max number for the width of the popup // let max_item = list_widths.iter().max().unwrap().to_owned(); - let max_item = - list_widths.0.chars().count() as u16 + list_widths.1.chars().count() as u16; - + let max_item = list_widths.0.clone() + &list_widths.1; + // list_widths.0.chars().count() as u16 + list_widths.1.chars().count() as u16; + + let fitting_width: u16 = { + let lines = vec![title, bottom_info, &max_item]; + let lline = lines + .iter() + .max_by(|a, b| a.chars().count().cmp(&b.chars().count())) + .unwrap(); + // lines.first().unwrap().chars().count() as u16 + lline.chars().count() as u16 + }; // Check if the popup would exceed the terminal frame width - let popup_width = if max_item + 2 > frame.area().width - 2 { + let popup_width = if fitting_width + 2 > frame.area().width - 2 { frame.area().width - 2 } else { - max_item + 2 + fitting_width + 2 }; + // } else if title.chars().count() as u16 > max_item { + // (title.chars().count() + 2) as u16 + // } else { + // max_item + 2 + // }; let popup_heigth = list.len() + 2; let popup_area = popup_area(frame.area(), popup_width, popup_heigth as u16); -- cgit v1.2.3 From e27069540a0bb22640974d0bd1a1bdf153b1b40d Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sat, 5 Jul 2025 22:32:41 +0200 Subject: succesfully tested note creation --- src/bibiman.rs | 1 + tests/note-files/doody.md | 0 2 files changed, 1 insertion(+) create mode 100644 tests/note-files/doody.md (limited to 'src/bibiman.rs') diff --git a/src/bibiman.rs b/src/bibiman.rs index 71ac831..87963dc 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -774,6 +774,7 @@ impl Bibiman { File::create_new(new_file).unwrap(); self.close_popup(); + self.update_lists(cfg); Ok(()) } diff --git a/tests/note-files/doody.md b/tests/note-files/doody.md new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3 From e2b4e12cf1ce15a26172ac8f2166c5e02ca89351 Mon Sep 17 00:00:00 2001 From: lukeflo Date: Sat, 5 Jul 2025 22:55:46 +0200 Subject: quit creating note function if citekey contains special char --- src/app.rs | 24 ++++++++++++++++++++++-- src/bibiman.rs | 7 +++++-- 2 files changed, 27 insertions(+), 4 deletions(-) (limited to 'src/bibiman.rs') diff --git a/src/app.rs b/src/app.rs index 01424bc..708ec37 100644 --- a/src/app.rs +++ b/src/app.rs @@ -427,7 +427,27 @@ impl App { } CmdAction::CreateNote => { if let CurrentArea::EntryArea = self.bibiman.current_area { - if cfg.general.note_path.is_some() + let citekey = self.bibiman.entry_table.entry_table_items[self + .bibiman + .entry_table + .entry_table_state + .selected() + .unwrap()] + .citekey + .clone(); + if citekey.contains("/") + | citekey.contains("|") + | citekey.contains("#") + | citekey.contains("\\") + | citekey.contains("*") + { + self.bibiman.open_popup( + PopupKind::MessageError, + Some("Selected entrys citekey contains special char: "), + Some(&citekey), + None, + )?; + } else if cfg.general.note_path.is_some() && cfg.general.note_extensions.is_some() && self.bibiman.entry_table.entry_table_items[self .bibiman @@ -454,7 +474,7 @@ impl App { )); } self.bibiman - .open_popup(PopupKind::CreateNote, None, None, Some(items)); + .open_popup(PopupKind::CreateNote, None, None, Some(items))?; } else if cfg.general.note_path.is_some() && self.bibiman.entry_table.entry_table_items[self .bibiman diff --git a/src/bibiman.rs b/src/bibiman.rs index 87963dc..6d21f8c 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -755,13 +755,15 @@ impl Bibiman { pub fn create_note(&mut self, cfg: &BibiConfig) -> Result<()> { // Index of selected entry let entry_idx = self.entry_table.entry_table_state.selected().unwrap(); - let citekey = self.entry_table.entry_table_items[entry_idx].citekey(); + let citekey = self.entry_table.entry_table_items[entry_idx] + .citekey + .clone(); // Index of selected popup field let popup_idx = self.popup_area.popup_state.selected().unwrap(); let ext = self.popup_area.popup_list[popup_idx].1.clone(); - let basename = PathBuf::from(citekey).with_extension(ext); + let basename = PathBuf::from(&citekey).with_extension(ext); let path = cfg.general.note_path.as_ref().unwrap(); let new_file = path.join(basename); @@ -775,6 +777,7 @@ impl Bibiman { File::create_new(new_file).unwrap(); self.close_popup(); self.update_lists(cfg); + self.select_entry_by_citekey(&citekey); Ok(()) } -- cgit v1.2.3