aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlukeflo2025-07-07 15:17:19 +0200
committerlukeflo2025-07-07 15:17:19 +0200
commitd55cfd8617410545335aeaf895120044c46dde45 (patch)
treebb4a41d32a3715610b8429047a12ec9a6540ee08
parent2e4ae7a05a73cb1df63343e14fbb8a5fd3c7bf41 (diff)
parentc0970da999e222cadbcc2242fb67686ed5b00ca4 (diff)
downloadbibiman-d55cfd8617410545335aeaf895120044c46dde45.tar.gz
bibiman-d55cfd8617410545335aeaf895120044c46dde45.zip
Merge pull request 'bibnotes' (#49) from bibnotes into main
- connect bibentries with notes through citekey<>basename matching - use multiple file extensions for note files (plain-text heavily recommended) - create notes for bibentries inside notes dir - use custom symbols as marker for files/links/notes attached to a bibentry
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--README.md75
-rw-r--r--src/app.rs156
-rw-r--r--src/bibiman.rs197
-rw-r--r--src/bibiman/bibisetup.rs282
-rw-r--r--src/bibiman/entries.rs7
-rw-r--r--src/bibiman/search.rs1
-rw-r--r--src/cliargs.rs117
-rw-r--r--src/config.rs54
-rw-r--r--src/tui/commands.rs4
-rw-r--r--src/tui/popup.rs19
-rw-r--r--src/tui/ui.rs230
-rw-r--r--tests/biblatex-test.bib12
-rw-r--r--tests/note-files/aristotle_physics.md0
-rw-r--r--tests/note-files/aristotle_poetics.txt1
-rw-r--r--tests/note-files/aristotle_rhetoric.md0
-rw-r--r--tests/note-files/augustine.md8
-rw-r--r--tests/note-files/bertram.txt1
-rw-r--r--tests/note-files/doody.md0
-rw-r--r--tests/pdf-files/annotated-pdfs/ARIStotle_rheTORIC.PDF (renamed from tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF)bin25294 -> 25294 bytes
-rw-r--r--tests/pdf-files/aristotle_physics.pdf (renamed from tests/pdf-files/aristotle:physics.pdf)bin25294 -> 25294 bytes
-rw-r--r--tests/pdf-files/aristotle_rhetoric.pdf (renamed from tests/pdf-files/aristotle:rhetoric.pdf)bin25294 -> 25294 bytes
-rw-r--r--tests/pdf-files/aristotle_rhetoric.txt0
-rw-r--r--tests/test-config.toml21
25 files changed, 936 insertions, 251 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5ecdfd4..696213a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -104,6 +104,7 @@ dependencies = [
"itertools",
"lexopt",
"nucleo-matcher",
+ "owo-colors",
"rand",
"ratatui",
"regex",
diff --git a/Cargo.toml b/Cargo.toml
index 18db28d..899ac61 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,3 +38,4 @@ ureq = "2.12.1"
# config = { version = "0.15.8", default-features = false, features = ["async", "async-trait", "convert-case", "convert_case", "toml"] }
serde = { version = "1.0.217", features = ["serde_derive"] }
figment = { version = "0.10.19", features = [ "toml", "test" ]}
+owo-colors = "4.2.2"
diff --git a/README.md b/README.md
index 5bc40b3..f5b7648 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@
- [Search](#search)
- [Edit bib entry](#edit-bib-entry)
- [Open connected files or links](#open-connected-files-or-links)
+ - [Note file creation](#note-file-creation)
- [Issues and code improvement](#issues-and-code-improvement)
- [Alternatives](#alternatives)
- [Comparison](#comparison)
@@ -45,6 +46,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<a name="installation"></a>
### Crates.io<a name="cratesio"></a>
@@ -191,6 +194,8 @@ set through the CLI, `bibiman` will offer to create a default config file at the
standard location. This will very likely happen on the first run of `bibiman`
after installation. If rejected, you probably will be asked again next time.
+The created config contains all values which are set as default by `bibiman`.
+
### General Configuration<a name="general-configuration"></a>
The following general values can be set through the config file:
@@ -200,19 +205,35 @@ The following general values can be set through the config file:
# Default files/dirs which are loaded on startup
# Use absolute paths (~ for HOME works). Otherwise, loading might not work.
bibfiles = [ "/path/to/bibfile", "path/to/dir/with/bibfiles" ]
+
# Default editor to use when editing files. Arguments are possible
editor = "vim" # with args: "vim -y"
+
# Default app to open PDFs/Epubs
pdf_opener = "xdg-open"
+
# Default app to open URLs/DOIs
url_opener = "xdg-open"
+
# Prefix which is prepended to the filepath from the `file` field
# Use absolute paths (~ for HOME works). Otherwise, loading might not work.
file_prefix = "/some/path/prefix"
+
# Path to folder (with subfolders) containing PDF files with the basename
# 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 +267,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<a name="color-configuration"></a>
@@ -270,8 +292,15 @@ info_color = "99"
confirm_color = "47"
warn_color = "124"
bar_bg_color = "234"
+popup_fg_color = "43"
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 +338,8 @@ 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.
+- [x] **Create note file** for bib entries.
- [ ] **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)).
@@ -335,6 +365,7 @@ Use the following keybindings to manage the TUI:
| `e` | Open editor at selected entry |
| `a` | Add entry through DOI |
| `o` | Open related PDF or URL/DOI |
+| `n` | Create new note file for selected entry |
| `TAB` | Switch between entries and keywords |
| `/`, `Ctrl-f` | Enter search mode |
| `Enter` | Filter by selected keyword / Confirm search or selection |
@@ -346,7 +377,8 @@ 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<a name="search"></a>
@@ -384,12 +416,12 @@ thus, there might be unexpected errors with it.
## Open connected files or links<a name="open-connected-files-or-links"></a>
-`bibiman` also provides the possibility to open PDFs (as value of the `file`
-BibLaTeX field), as well as DOIs and URLs.
+`bibiman` also provides the possibility to open PDFs , note files, as well as
+DOIs and URLs connected with the different entries of the bibfile.
For selecting the right program, it uses `xdg-open` on Linux, `open` on MacOS,
-and `start` on Windows. Thanks to the report from @bastislack in #2 MacOS seems
-to work.
+and `start` on Windows by default. Thanks to the report from @bastislack in #2
+MacOS seems to work.
_However, Windows does not work. Have to figure this out. Reports from some
Windows users are very welcome._
@@ -398,6 +430,25 @@ Furthermore, DOIs have to begin with either `https://doi...` as full URL or
`10.(...)` as regular DOI style. URLs work if they begin with either `http...`
or with `www...`.
+## Note file creation<a name="note-file-creation"></a>
+
+It is possible to create notes for an entry missing such a file. The `note_path`
+and `note_extensions` values need to be set in the config file or it will fail.
+
+The notes basename is *always* the citekey of the selected entry and the
+directory is set to the value of the `note_path` variable. The extension can be
+choosen from one of the file format extension set in the `note_extensions`
+array.
+
+**Be aware**: The operation of creating new notes is not permitted if the
+citekey contains some special chars which could cause problems with Unixish
+shell commands and file operations. Currently, the following chars are not
+allowed as part of the citekey: `/` | `|` | `#` | `*` | `\` | `"` | `'` | `;` |
+`!`
+
+The bibfile itself will *not be edited*. Therefore, you can't break anything in
+your bibfile with this operation!
+
## Issues and code improvement<a name="issues-and-code-improvement"></a>
This is my first Rust project and, thus, also a learning process. If you find
diff --git a/src/app.rs b/src/app.rs
index d10e4f8..8b76f17 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -16,14 +16,14 @@
/////
use crate::bibiman::CurrentArea;
-use crate::config::BibiConfig;
-use color_eyre::eyre::{Context, Ok, Result};
-// use super::Event;
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};
+use crossterm::event::KeyCode;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::process::{Command, Stdio};
@@ -82,7 +82,13 @@ 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)?;
+ // 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))
@@ -186,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)
}
_ => {}
@@ -207,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)
}
_ => {}
@@ -267,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();
@@ -279,12 +289,14 @@ 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)?
} 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)?
}
}
}
@@ -305,13 +317,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::Citekey,
+ )];
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(),
@@ -342,18 +366,20 @@ impl App {
.selected()
.unwrap();
let entry = self.bibiman.entry_table.entry_table_items[idx].clone();
- let mut items: Vec<(String, String)> = vec![];
- if entry.filepath.is_some() || entry.doi_url.is_some() {
+ 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(),
+ "Link: ".into(),
entry.doi_url.unwrap().clone(),
+ PopupItem::Link,
))
}
if entry.filepath.is_some() {
entry.filepath.unwrap().iter().for_each(|p| {
items.push((
- "File (PDF/EPUB): ".into(),
+ "File: ".into(),
// p.clone().into_string().unwrap(),
if entry.file_field && cfg.general.file_prefix.is_some() {
cfg.general
@@ -367,9 +393,19 @@ 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(),
+ PopupItem::Notefile,
+ ));
+ });
+ }
self.bibiman
.open_popup(PopupKind::OpenRes, None, None, Some(items))?;
@@ -389,6 +425,87 @@ impl App {
self.bibiman.add_entry();
}
}
+ CmdAction::CreateNote => {
+ if let CurrentArea::EntryArea = self.bibiman.current_area {
+ let citekey = self.bibiman.entry_table.entry_table_items[self
+ .bibiman
+ .entry_table
+ .entry_table_state
+ .selected()
+ .unwrap()]
+ .citekey
+ .clone();
+ // disallow chars which can cause other shell executions
+ if citekey.contains("/")
+ | citekey.contains("|")
+ | citekey.contains("#")
+ | citekey.contains("\\")
+ | 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
+ .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)?;
}
@@ -404,15 +521,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???)
diff --git a/src/bibiman.rs b/src/bibiman.rs
index 21601e3..6d21f8c 100644
--- a/src/bibiman.rs
+++ b/src/bibiman.rs
@@ -20,21 +20,22 @@ 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};
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;
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;
@@ -160,7 +161,7 @@ impl Bibiman {
popup_kind: PopupKind,
message: Option<&str>,
object: Option<&str>,
- items: Option<Vec<(String, String)>>,
+ items: Option<Vec<(String, String, PopupItem)>>,
) -> Result<()> {
if let CurrentArea::EntryArea = self.current_area {
self.former_area = Some(FormerArea::EntryArea);
@@ -237,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",
+ ))
+ }
+ }
}
}
@@ -366,9 +379,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();
}
@@ -395,7 +408,7 @@ impl Bibiman {
.entry_table_state
.selected_column()
.unwrap()
- == 0
+ == 1
{
self.entry_table.entry_table_state.select_last_column();
} else {
@@ -512,6 +525,74 @@ 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
+
+ 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()?;
+
+ // 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);
@@ -562,10 +643,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((
@@ -576,6 +665,7 @@ impl Bibiman {
.to_str()
.unwrap()
.to_owned(),
+ PopupItem::Bibfile,
));
}
self.popup_area.popup_selection(items);
@@ -662,7 +752,36 @@ impl Bibiman {
Ok(())
}
- pub fn open_connected_res(&mut self, cfg: &BibiConfig) -> Result<()> {
+ 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
+ .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 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();
+ self.update_lists(cfg);
+ self.select_entry_by_citekey(&citekey);
+ 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();
@@ -671,12 +790,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()));
@@ -692,6 +811,20 @@ impl Bibiman {
None,
)?;
}
+ } 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() {
+ 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,
+ )?;
+ }
} else {
eprintln!("Unable to find ressource to open");
};
@@ -733,11 +866,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 {
@@ -747,7 +886,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())?;
@@ -762,6 +913,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/bibisetup.rs b/src/bibiman/bibisetup.rs
index bf5baf5..1f8a912 100644
--- a/src/bibiman/bibisetup.rs
+++ b/src/bibiman/bibisetup.rs
@@ -55,12 +55,23 @@ pub struct BibiData {
pub filepath: Option<Vec<OsString>>,
pub file_field: bool,
pub subtitle: Option<String>,
+ pub notes: Option<Vec<OsString>>,
+ pub symbols: [Option<String>; 3],
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct BibiRow<'a> {
+ pub authors: &'a str,
+ pub title: &'a str,
+ pub year: &'a str,
+ pub pubtype: &'a str,
+ pub symbols: &'a [Option<String>; 3],
}
impl BibiData {
// This functions decides which fields are rendered in the entry table
// Fields which should be usable but not visible can be left out
- pub fn ref_vec(&mut self) -> Vec<&str> {
+ pub fn ref_vec(&mut self, cfg: &BibiConfig) -> BibiRow {
self.short_author = match self.authors.split_once(",") {
Some((first, _rest)) => {
if self.authors().contains("(ed.)") {
@@ -74,18 +85,35 @@ impl BibiData {
None => String::from(""),
};
- vec![
- {
+ self.symbols = self.create_symbols(cfg);
+
+ // vec![
+ // {
+ // if self.short_author.is_empty() {
+ // self.authors()
+ // } else {
+ // &self.short_author
+ // }
+ // },
+ // self.title(),
+ // self.year(),
+ // self.pubtype(),
+ // &self.symbols,
+ // ]
+
+ BibiRow {
+ authors: {
if self.short_author.is_empty() {
self.authors()
} else {
&self.short_author
}
},
- self.title(),
- self.year(),
- self.pubtype(),
- ]
+ title: self.title(),
+ year: self.year(),
+ pubtype: self.pubtype(),
+ symbols: &self.symbols,
+ }
}
pub fn entry_id(&self) -> &u32 {
@@ -131,6 +159,26 @@ impl BibiData {
pub fn subtitle(&self) -> &str {
self.subtitle.as_ref().unwrap()
}
+
+ fn create_symbols(&self, cfg: &BibiConfig) -> [Option<String>; 3] {
+ [
+ if self.file_field || self.filepath.is_some() {
+ Some(cfg.general.file_symbol.clone())
+ } else {
+ None
+ },
+ if self.doi_url.is_some() {
+ Some(cfg.general.link_symbol.clone())
+ } else {
+ None
+ },
+ if self.notes.is_some() {
+ Some(cfg.general.note_symbol.clone())
+ } else {
+ None
+ },
+ ]
+ }
}
impl BibiSetup {
@@ -200,10 +248,26 @@ impl BibiSetup {
cfg: &BibiConfig,
) -> Vec<BibiData> {
let mut pdf_files = if cfg.general.pdf_path.is_some() {
- collect_pdf_file_paths(cfg.general.pdf_path.as_ref().unwrap())
+ collect_file_paths(cfg.general.pdf_path.as_ref().unwrap(), &Some(vec!["pdf"]))
} else {
None
};
+ let ext: Option<Vec<&str>> =
+ if cfg.general.note_path.is_some() && cfg.general.note_extensions.is_some() {
+ let mut ext: Vec<&str> = Vec::new();
+ for e in cfg.general.note_extensions.as_ref().unwrap().iter() {
+ ext.push(e);
+ }
+ Some(ext)
+ } else {
+ None
+ };
+ let mut note_files =
+ if cfg.general.note_path.is_some() && cfg.general.note_extensions.is_some() {
+ collect_file_paths(cfg.general.note_path.as_ref().unwrap(), &ext)
+ } else {
+ None
+ };
citekeys
.iter()
.enumerate()
@@ -225,6 +289,12 @@ impl BibiSetup {
filepath: filepaths.0,
file_field: filepaths.1,
subtitle: Self::get_subtitle(k, bibliography),
+ notes: if note_files.is_some() {
+ Self::get_notepath(k, &mut note_files, &ext)
+ } else {
+ None
+ },
+ symbols: [None, None, None],
}
})
.collect()
@@ -373,12 +443,27 @@ impl BibiSetup {
true,
)
} else if pdf_files.is_some() {
- (Self::merge_filepath_or_none(&citekey, pdf_files), false)
+ (
+ Self::merge_filepath_or_none_two(&citekey, pdf_files, vec!["pdf"]),
+ false,
+ )
} else {
(None, false)
}
}
+ pub fn get_notepath(
+ citekey: &str,
+ note_files: &mut Option<HashMap<String, Vec<PathBuf>>>,
+ ext: &Option<Vec<&str>>,
+ ) -> Option<Vec<OsString>> {
+ if let Some(e) = ext {
+ Self::merge_filepath_or_none_two(citekey, note_files, e.to_vec())
+ } else {
+ None
+ }
+ }
+
pub fn get_subtitle(citekey: &str, biblio: &Bibliography) -> Option<String> {
if biblio.get(citekey).unwrap().subtitle().is_ok() {
Some(
@@ -394,99 +479,76 @@ impl BibiSetup {
}
}
- fn merge_filepath_or_none(
+ /// Check if there exists files with the basename of the format
+ /// "citekey.extension" in the passed hashmap. If so, return all matches
+ /// as `Option<Vec>`, otherwise return `None`
+ fn merge_filepath_or_none_two(
citekey: &str,
- pdf_files: &mut Option<HashMap<String, Vec<PathBuf>>>,
+ files: &mut Option<HashMap<String, Vec<PathBuf>>>,
+ extensions: Vec<&str>,
) -> Option<Vec<OsString>> {
- let pdf_file = {
- // let mut idx = 0;
- let citekey = citekey.to_owned().to_ascii_lowercase() + ".pdf";
- // let filename = citekey.to_owned() + ".pdf";
- // for f in args.pdf_files.unwrap().iter() {
- // if f.file_name().unwrap().to_str().unwrap() == &filename {
- // break f;
- // }
- // }
-
- // loop {
- // if idx + 1 > pdf_files.as_ref().unwrap().len() {
- // break None;
- // }
- // let cur_entry = pdf_files.as_ref().unwrap()[idx].clone();
- // if cur_entry.is_file()
- // && cur_entry
- // .file_name()
- // .unwrap()
- // .to_ascii_lowercase()
- // .to_str()
- // .unwrap()
- // == citekey
- // {
- // let path = cur_entry.to_owned().into_os_string();
- // pdf_files.as_mut().unwrap().swap_remove(idx);
- // break Some(path);
- // } else {
- // idx += 1
- // }
- // }
-
- // for file in pdf_files.as_ref().unwrap().iter() {
- // let filename = file.file_name().unwrap().to_ascii_lowercase();
- // if filename.to_str().unwrap() == citekey {
- // break;
- // } else if pdf_files.as_ref().unwrap().len() > idx {
- // break;
- // } else {
- // idx += 1;
- // }
- // }
-
- // if pdf_files.as_ref().unwrap()[idx].is_file() {
- // Some(pdf_files.as_ref().unwrap()[idx].to_owned().into_os_string())
- // } else {
- // None
- // }
-
- if pdf_files.as_ref().unwrap().contains_key(&citekey) {
- let path_vec = pdf_files
+ let mut file = Vec::new();
+
+ for e in extensions.iter() {
+ let basename = citekey.to_owned().to_ascii_lowercase() + "." + e;
+ if files.as_ref().unwrap().contains_key(&basename) {
+ let _ = files
.as_ref()
.unwrap()
- .get(&citekey)
+ .get(&basename)
.unwrap()
- .to_owned();
- Some(path_vec.into_iter().map(|p| p.into_os_string()).collect())
- } else {
- None
+ .to_owned()
+ .into_iter()
+ .for_each(|p| file.push(p.into_os_string()));
}
- };
+ }
- pdf_file
+ if file.is_empty() {
+ None
+ } else {
+ Some(file)
+ }
}
}
-/// This function walks the given dir and collects all pdf files into a `HashMap`
-/// of the format `[String, Vec<PathBuf>]`, where `String` represents the basename
-/// of the file and the `Vec<PathBuf>` holds all filepaths ending with this basename.
+/// This function walks the given dir and collects all files matching one of the
+/// passed extensions into a `HashMap` of the format `[String, Vec<PathBuf>]`,
+/// where `String` represents the basename of the file and the `Vec<PathBuf>` holds
+/// all filepaths ending with this basename.
///
/// In most cases the latter is only a single path, but there might be some concepts
/// with subdirs were some entries have multiple files associated with them.
-pub fn collect_pdf_file_paths(pdf_dir: &PathBuf) -> Option<HashMap<String, Vec<PathBuf>>> {
+///
+/// Passing [`None`] as argument for extensions will result in collecting all files
+/// from the given directory and its subdirectories!
+pub fn collect_file_paths(
+ file_dir: &PathBuf,
+ extensions: &Option<Vec<&str>>,
+) -> Option<HashMap<String, Vec<PathBuf>>> {
let mut files: HashMap<String, Vec<PathBuf>> = HashMap::new();
// Expand tilde to /home/user
- let pdf_dir = if pdf_dir.starts_with("~") {
- &app::expand_home(&pdf_dir)
+ let file_dir = if file_dir.starts_with("~") {
+ &app::expand_home(&file_dir)
} else {
- pdf_dir
+ file_dir
};
// Walk the passed dir and collect all pdf files into hashmap
- if pdf_dir.is_dir() {
- for file in WalkDir::new(pdf_dir) {
+ if file_dir.is_dir() {
+ for file in WalkDir::new(file_dir) {
let f = file.unwrap().into_path();
if f.is_file()
&& f.extension().is_some()
- && f.extension().unwrap_or_default().to_ascii_lowercase() == "pdf"
+ && extensions.as_ref().is_some_and(|v| {
+ v.contains(
+ &f.extension()
+ .unwrap_or_default()
+ .to_ascii_lowercase()
+ .to_str()
+ .unwrap_or_default(),
+ )
+ })
{
let filename = f
.file_name()
@@ -500,6 +562,19 @@ pub fn collect_pdf_file_paths(pdf_dir: &PathBuf) -> Option<HashMap<String, Vec<P
} else {
files.insert(filename, vec![f]);
}
+ } else if f.is_file() && extensions.is_none() {
+ let filename = f
+ .file_name()
+ .unwrap()
+ .to_ascii_lowercase()
+ .into_string()
+ .unwrap();
+
+ if let Some(paths) = files.get_mut(&filename) {
+ paths.push(f);
+ } else {
+ files.insert(filename, vec![f]);
+ }
}
}
}
@@ -510,3 +585,52 @@ pub fn collect_pdf_file_paths(pdf_dir: &PathBuf) -> Option<HashMap<String, Vec<P
Some(files)
}
}
+
+#[cfg(test)]
+mod tests {
+ use std::{collections::HashMap, ffi::OsString, path::PathBuf};
+
+ use super::BibiSetup;
+
+ #[test]
+ fn check_file_matching() {
+ let mut files: HashMap<String, Vec<PathBuf>> = HashMap::new();
+ files.insert(
+ "citekey.md".to_string(),
+ vec![
+ PathBuf::from("/one/note/citekey.md"),
+ PathBuf::from("/one/other/citekey.md"),
+ ],
+ );
+ files.insert(
+ "citekey.pdf".to_string(),
+ vec![
+ PathBuf::from("/one/note/citekey.pdf"),
+ PathBuf::from("/one/other/citekey.pdf"),
+ ],
+ );
+ files.insert(
+ "citekey2.pdf".to_string(),
+ vec![
+ PathBuf::from("/one/note/citekey2.pdf"),
+ PathBuf::from("/one/other/citekey2.pdf"),
+ ],
+ );
+
+ let matches =
+ BibiSetup::merge_filepath_or_none_two("citekey", &mut Some(files), vec!["md", "pdf"]);
+
+ assert_eq!(
+ matches.clone().unwrap().iter().next().unwrap().to_owned(),
+ OsString::from("/one/note/citekey.md")
+ );
+ assert_eq!(
+ matches.clone().unwrap().last().unwrap().to_owned(),
+ OsString::from("/one/other/citekey.pdf")
+ );
+ assert!(!matches
+ .clone()
+ .unwrap()
+ .contains(&OsString::from("/one/other/citekey2.pdf")));
+ }
+}
diff --git a/src/bibiman/entries.rs b/src/bibiman/entries.rs
index 88a1583..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 {
@@ -159,6 +160,7 @@ mod tests {
filepath: None,
file_field: false,
subtitle: None,
+ notes: None,
};
let entry_vec = BibiData::ref_vec(&mut entry);
@@ -177,6 +179,7 @@ mod tests {
filepath: None,
file_field: false,
subtitle: None,
+ notes: None,
};
let entry_vec_editors = BibiData::ref_vec(&mut entry_editors);
diff --git a/src/bibiman/search.rs b/src/bibiman/search.rs
index f391aed..0e32f63 100644
--- a/src/bibiman/search.rs
+++ b/src/bibiman/search.rs
@@ -137,6 +137,7 @@ mod tests {
filepath: Some(vec![OsString::from("/home/file/path.pdf")]),
file_field: true,
subtitle: None,
+ notes: None,
};
let joined_vec = BibiSearch::convert_to_string(&bibvec);
diff --git a/src/cliargs.rs b/src/cliargs.rs
index 114b15a..082ecda 100644
--- a/src/cliargs.rs
+++ b/src/cliargs.rs
@@ -16,9 +16,11 @@
/////
use color_eyre::eyre::Result;
-use color_eyre::owo_colors::OwoColorize;
use dirs::{config_dir, home_dir};
use lexopt::prelude::*;
+use owo_colors::colors::css::LightGreen;
+use owo_colors::colors::*;
+use owo_colors::OwoColorize;
use std::env;
use std::path::PathBuf;
use walkdir::WalkDir;
@@ -112,49 +114,82 @@ pub fn parse_files(args: Vec<PathBuf>) -> Vec<PathBuf> {
}
pub fn help_func() -> String {
- let help = format!(
- "\
-{} {}
-
-USAGE:
- bibiman [FLAGS] [files/dirs]
-
-POSITIONAL ARGS:
- <file> Path to .bib file
- <dir> Path to directory containing .bib files
-
- Both can be passed multiple times
-
-FLAGS:
- -h, --help Show this help and exit
- -v, --version Show the version and exit
- -c, --config-file=<value> Path to config file used for current session.
- Takes precedence over standard config file.
- --light-terminal Enable color mode for light terminal background
- --pdf-path=<value> Use PDF files named by citekey at the given path and its
- subdirs as value for the `file` field of the entry matching
- the citekey for the current session.
- Does not overwrite or change the original file.
- (might not work with citekeys containing special chars)",
- env!("CARGO_PKG_NAME"),
- env!("CARGO_PKG_VERSION"),
- );
+ let help = vec![
+ format!(
+ "{} {}\n",
+ env!("CARGO_PKG_NAME").fg::<Green>().bold(),
+ env!("CARGO_PKG_VERSION").fg::<LightGreen>(),
+ ),
+ format!(
+ "{}:\n\t{} [Flags] [files/dirs]\n",
+ "USAGE".bold(),
+ "bibiman".bold()
+ ),
+ format!(
+ "{}:\n\t{}\t\tPath to {} file",
+ "POSITIONAL ARGUMENTS".bold(),
+ "<file>".fg::<BrightMagenta>().bold(),
+ ".bib".fg::<BrightBlack>().bold()
+ ),
+ format!(
+ "\t{}\tPath to directory containing {} files",
+ "<directory>".fg::<BrightBlue>().bold(),
+ ".bib".fg::<BrightBlack>().bold()
+ ),
+ format!("\n\t{}", "Both can be passed multiple times".italic()),
+ format!("\n{}:", "FLAGS".bold()),
+ format!("\t{}", "-h, --help".bold().fg::<BrightCyan>()),
+ format!("\t\t{}", "Show this help and exit"),
+ format!("\t{}", "-v, --version".bold().fg::<BrightCyan>()),
+ format!("\t\t{}", "Show the version and exit"),
+ format!("\t{}", "--light-terminal".bold().fg::<BrightCyan>()),
+ format!(
+ "\t\t{}",
+ "Enable default colors for light terminal background"
+ ),
+ format!(
+ "\t{}{}",
+ "-c, --config-file=".bold().fg::<BrightCyan>(),
+ "<value>".bold().italic().fg::<BrightCyan>()
+ ),
+ format!("\t\t{}", "Path to config file used for current session."),
+ format!("\t\t{}", "Takes precedence over standard config file."),
+ format!(
+ "\t{}{}",
+ "--pdf-path=".bold().fg::<BrightCyan>(),
+ "<value>".bold().italic().fg::<BrightCyan>()
+ ),
+ format!("\t\t{}", "Path to directory containing PDF files."),
+ format!(
+ "\t\t{}",
+ "If the pdf files basename matches an entrys citekey,"
+ ),
+ format!(
+ "\t\t{}",
+ "its attached as connected PDF file for the current session."
+ ),
+ format!("\t\t{}", "Does not edit the bibfile itself!"),
+ ];
+ let help = help.join("\n");
help
}
pub fn version_func() -> String {
- let version = format!(
- "\
-{} {}
-{}
-{}
-
-Target Triple: {}",
- env!("CARGO_PKG_NAME"),
- env!("CARGO_PKG_VERSION"),
- env!("CARGO_PKG_AUTHORS"),
- env!("CARGO_PKG_LICENSE"),
- env!("TARGET")
- );
+ let version: Vec<String> = vec![
+ format!(
+ "{} {}",
+ env!("CARGO_PKG_NAME").fg::<Green>().bold(),
+ env!("CARGO_PKG_VERSION").fg::<LightGreen>()
+ ),
+ format!("{}", env!("CARGO_PKG_AUTHORS").bold()),
+ format!("{}", env!("CARGO_PKG_LICENSE")),
+ format!("\n"),
+ format!(
+ "{} {}",
+ "Target Triple:".bold(),
+ env!("TARGET").fg::<BrightRed>()
+ ),
+ ];
+ let version = version.join("\n");
version
}
diff --git a/src/config.rs b/src/config.rs
index f5f2dd0..1f6c619 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -56,6 +56,17 @@ const DEFAULT_CONFIG: &str = r##"
## 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/notes/folder"
+# note_extensions = [ "md", "txt", "org" ]
+
+## Symbols/chars to show if not has specific attachement
+# note_symbol = "N"
+# file_symbol = "F"
+# link_symbol = "L"
+
# [colors]
## Default values for dark-themed terminal
## Possible values are:
@@ -70,8 +81,15 @@ const DEFAULT_CONFIG: &str = r##"
# confirm_color = "47"
# warn_color = "124"
# bar_bg_color = "234"
+# popup_fg_color = "43"
# popup_bg_color = "234"
# selected_row_bg_color = "237"
+# note_color = "123"
+# file_color = "209"
+# link_color = "39"
+# author_color = "38"
+# title_color = "37"
+# year_color = "135"
"##;
/// Main struct of the config file. Contains substructs/headings in toml
@@ -90,6 +108,11 @@ pub struct General {
pub url_opener: String,
pub file_prefix: Option<PathBuf>,
pub pdf_path: Option<PathBuf>,
+ pub note_path: Option<PathBuf>,
+ pub note_extensions: Option<Vec<String>>,
+ pub note_symbol: String,
+ pub file_symbol: String,
+ pub link_symbol: String,
}
/// Substruct [colors] in config.toml
@@ -103,8 +126,15 @@ pub struct Colors {
pub confirm_color: Color,
pub warn_color: Color,
pub bar_bg_color: Color,
+ pub popup_fg_color: Color,
pub popup_bg_color: Color,
pub selected_row_bg_color: Color,
+ pub note_color: Color,
+ pub file_color: Color,
+ pub link_color: Color,
+ pub author_color: Color,
+ pub title_color: Color,
+ pub year_color: Color,
}
impl Default for BibiConfig {
@@ -117,6 +147,11 @@ impl Default for BibiConfig {
url_opener: select_opener(),
file_prefix: None,
pdf_path: None,
+ note_path: None,
+ note_extensions: None,
+ note_symbol: String::from("N"),
+ file_symbol: String::from("F"),
+ link_symbol: String::from("L"),
},
colors: Self::dark_colors(),
}
@@ -133,6 +168,11 @@ impl BibiConfig {
url_opener: select_opener(),
file_prefix: None,
pdf_path: None,
+ note_path: None,
+ note_extensions: None,
+ note_symbol: String::from("N"),
+ file_symbol: String::from("F"),
+ link_symbol: String::from("L"),
},
colors: if args.light_theme {
Self::light_colors()
@@ -173,8 +213,15 @@ impl BibiConfig {
confirm_color: Color::Indexed(47),
warn_color: Color::Indexed(124),
bar_bg_color: Color::Indexed(235),
+ popup_fg_color: Color::Indexed(43),
popup_bg_color: Color::Indexed(234),
selected_row_bg_color: Color::Indexed(237),
+ note_color: Color::Indexed(123),
+ file_color: Color::Indexed(209),
+ link_color: Color::Indexed(39),
+ author_color: Color::Indexed(38),
+ title_color: Color::Indexed(37),
+ year_color: Color::Indexed(135),
}
}
@@ -187,10 +234,17 @@ impl BibiConfig {
keyword_color: Color::Indexed(58),
info_color: Color::Indexed(57),
bar_bg_color: Color::Indexed(144),
+ popup_fg_color: Color::Indexed(43),
popup_bg_color: Color::Indexed(187),
confirm_color: Color::Indexed(22),
warn_color: Color::Indexed(124),
selected_row_bg_color: Color::Indexed(107),
+ note_color: Color::Indexed(123),
+ file_color: Color::Indexed(209),
+ link_color: Color::Indexed(27),
+ author_color: Color::Indexed(38),
+ title_color: Color::Indexed(37),
+ year_color: Color::Indexed(135),
}
}
diff --git a/src/tui/commands.rs b/src/tui/commands.rs
index 08ee677..89fcf44 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,
}
@@ -149,6 +151,8 @@ impl From<KeyEvent> 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/popup.rs b/src/tui/popup.rs
index 93b01c3..46e4792 100644
--- a/src/tui/popup.rs
+++ b/src/tui/popup.rs
@@ -38,6 +38,19 @@ 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)]
+pub enum PopupItem {
+ Bibfile,
+ Entryfile,
+ Notefile,
+ Citekey,
+ Link,
+ Default,
+ None,
}
#[derive(Debug, Default)]
@@ -46,7 +59,7 @@ pub struct PopupArea {
pub popup_kind: Option<PopupKind>,
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 +129,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 ebebe4c..69ca058 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;
@@ -273,21 +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<ListItem> = app
- .bibiman
- .popup_area
- .popup_list
- .iter()
- .map(
- |(mes, obj)| {
- ListItem::from(Line::from(vec![
- Span::styled(mes, Style::new().bold()),
- Span::raw(obj),
- ]))
- }, // ListItem::from(mes.to_owned() + obj)
- )
- .collect();
+ Some(PopupKind::OpenRes)
+ | Some(PopupKind::AppendToFile)
+ | Some(PopupKind::YankItem)
+ | Some(PopupKind::CreateNote) => {
+ let list_items: Vec<ListItem> = 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 "
@@ -295,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) ━ (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()
@@ -317,7 +347,7 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) {
.bg(cfg.colors.popup_bg_color),
)
.border_set(symbols::border::THICK)
- .border_style(Style::new().fg(cfg.colors.keyword_color));
+ .border_style(Style::new().fg(cfg.colors.popup_fg_color));
let list = List::new(list_items).block(block).highlight_style(
Style::new()
@@ -333,7 +363,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)
@@ -344,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);
@@ -580,6 +624,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("Res.")).bg(cfg.colors.bar_bg_color),
Cell::from(
Line::from(vec![{ Span::raw("Author") }, {
if let Some(EntryTableColumn::Authors) =
@@ -698,33 +743,74 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec
.iter_mut()
.enumerate()
.map(|(_i, data)| {
- let item = data.ref_vec();
- item.into_iter()
- .map(|content| Cell::from(Text::from(content.to_string())))
- .collect::<Row>()
- .style(
- // Style::new().fg(color_list(
- // args,
- // i as i32,
- // app.bibiman
- // .entry_table
- // .entry_table_state
- // .selected()
- // .unwrap_or(0) as i32,
- // args.colors.highlight_text_color,
- // 20,
- // )),
- Style::new().fg(if let CurrentArea::EntryArea = app.bibiman.current_area {
- cfg.colors.highlight_text_color
- } else {
- cfg.colors.main_text_color
- }),
- )
- .height(1)
+ let item = data.ref_vec(cfg);
+
+ 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![
+ 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)),
+ ]);
+
+ // let row = item
+ // .into_iter()
+ // .map(|content| Cell::from(Text::from(content.to_string())))
+ // .collect::<Row>();
+
+ row.style(
+ Style::new().fg(if let CurrentArea::EntryArea = app.bibiman.current_area {
+ cfg.colors.highlight_text_color
+ } else {
+ cfg.colors.main_text_color
+ }),
+ )
+ .height(1)
});
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(
@@ -793,7 +879,10 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
lines.push(Line::from(vec![
Span::styled("Authors: ", style_value),
// Span::styled(cur_entry.authors.clone(), Style::new().green()),
- Span::styled(cur_entry.authors(), Style::new().fg(cfg.colors.info_color)),
+ Span::styled(
+ cur_entry.authors(),
+ Style::new().fg(cfg.colors.author_color),
+ ),
]));
if cur_entry.subtitle.is_some() {
lines.push(Line::from(vec![
@@ -801,19 +890,19 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
Span::styled(
cur_entry.title(),
Style::new()
- .fg(cfg.colors.entry_color)
+ .fg(cfg.colors.title_color)
.add_modifier(Modifier::ITALIC),
),
Span::styled(
": ",
Style::new()
- .fg(cfg.colors.entry_color)
+ .fg(cfg.colors.title_color)
.add_modifier(Modifier::ITALIC),
),
Span::styled(
cur_entry.subtitle(),
Style::new()
- .fg(cfg.colors.entry_color)
+ .fg(cfg.colors.title_color)
.add_modifier(Modifier::ITALIC),
),
]));
@@ -823,14 +912,14 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
Span::styled(
cur_entry.title(),
Style::new()
- .fg(cfg.colors.entry_color)
+ .fg(cfg.colors.title_color)
.add_modifier(Modifier::ITALIC),
),
]));
}
lines.push(Line::from(vec![
Span::styled("Year: ", style_value),
- Span::styled(cur_entry.year(), Style::new().fg(cfg.colors.keyword_color)),
+ Span::styled(cur_entry.year(), Style::new().fg(cfg.colors.year_color)),
]));
// Render keywords in info box in Markdown code style
if !cur_entry.keywords.is_empty() {
@@ -873,7 +962,7 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
Span::styled("DOI/URL: ", style_value),
Span::styled(
cur_entry.doi_url(),
- Style::new().fg(cfg.colors.main_text_color).underlined(),
+ Style::new().fg(cfg.colors.link_color).underlined(),
),
]));
}
@@ -882,19 +971,19 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
Span::styled("File: ", style_value),
Span::styled(
p.iter().map(|f| f.to_str().unwrap()).join("; "),
- Style::new().fg(cfg.colors.main_text_color),
+ Style::new().fg(cfg.colors.file_color),
+ ),
+ ]));
+ }
+ if let Some(n) = &cur_entry.notes {
+ lines.push(Line::from(vec![
+ Span::styled("Note: ", style_value),
+ Span::styled(
+ n.iter().map(|n| n.to_str().unwrap()).join("; "),
+ Style::new().fg(cfg.colors.note_color),
),
]));
}
- // 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(),
@@ -913,7 +1002,12 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
// We show the list item's info under the list in this paragraph
let block = Block::bordered()
- .title(Line::raw(" Entry Information ").centered().bold())
+ .title(
+ Line::raw(" Entry Information ")
+ .centered()
+ .bold()
+ .fg(cfg.colors.info_color),
+ )
.border_set(symbols::border::PLAIN)
.border_style(Style::new().fg(cfg.colors.main_text_color))
.padding(Padding::horizontal(1));
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_physics.md b/tests/note-files/aristotle_physics.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/note-files/aristotle_physics.md
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
--- /dev/null
+++ b/tests/note-files/aristotle_rhetoric.md
diff --git a/tests/note-files/augustine.md b/tests/note-files/augustine.md
new file mode 100644
index 0000000..7af8fa6
--- /dev/null
+++ b/tests/note-files/augustine.md
@@ -0,0 +1,8 @@
+---
+title: Notes about Augustine
+author: Great scholar
+---
+
+# Augustine
+
+A crazy dude writing some interesting stuff
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/doody.md b/tests/note-files/doody.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/note-files/doody.md
diff --git a/tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF b/tests/pdf-files/annotated-pdfs/ARIStotle_rheTORIC.PDF
index 6aaba88..6aaba88 100644
--- a/tests/pdf-files/annotated-pdfs/ARIStotle:rheTORIC.PDF
+++ b/tests/pdf-files/annotated-pdfs/ARIStotle_rheTORIC.PDF
Binary files differ
diff --git a/tests/pdf-files/aristotle:physics.pdf b/tests/pdf-files/aristotle_physics.pdf
index 6aaba88..6aaba88 100644
--- a/tests/pdf-files/aristotle:physics.pdf
+++ b/tests/pdf-files/aristotle_physics.pdf
Binary files differ
diff --git a/tests/pdf-files/aristotle:rhetoric.pdf b/tests/pdf-files/aristotle_rhetoric.pdf
index 6aaba88..6aaba88 100644
--- a/tests/pdf-files/aristotle:rhetoric.pdf
+++ b/tests/pdf-files/aristotle_rhetoric.pdf
Binary files differ
diff --git a/tests/pdf-files/aristotle_rhetoric.txt b/tests/pdf-files/aristotle_rhetoric.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/pdf-files/aristotle_rhetoric.txt
diff --git a/tests/test-config.toml b/tests/test-config.toml
index 6d05b64..1d29043 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"
@@ -19,7 +19,18 @@ file_prefix = "/some/path/prefix"
## Path to folder (with subfolders) containing PDF files with the basename
## of the format "citekey.pdf". Other PDF basenames are not accepted.
## Use absolute paths (~ for HOME works). Otherwise, loading might not work.
-pdf_path = "~/Documents/coding/projects/bibiman/tests/pdf-files"
+pdf_path = "tests/pdf-files"
+
+## 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 = "tests/note-files"
+note_extensions = [ "md", "txt" ]
+
+## Symbols/chars to show if not has specific attachement
+file_symbol = " "
+link_symbol = "󰌹 "
+note_symbol = "󰧮"
# [colors]
## Default values for dark-themed terminal
@@ -37,3 +48,9 @@ pdf_path = "~/Documents/coding/projects/bibiman/tests/pdf-files"
# 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"