aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlukeflo2024-12-23 21:03:48 +0100
committerlukeflo2024-12-23 21:03:48 +0100
commit57126d7f42b871aa1835b056fbe74179c13e53b0 (patch)
treebf81db50446aa7a8e06553f4e8aff97ea0cfb816
parentad5c2cb586616eca99fc1db0efaaa0ff5aa97144 (diff)
parent9a33a794167d60ce35030f007674f6e9424b1ff3 (diff)
downloadbibiman-57126d7f42b871aa1835b056fbe74179c13e53b0.tar.gz
bibiman-57126d7f42b871aa1835b056fbe74179c13e53b0.zip
Merge branch 'add-entry-via-doi'
+ implement the functionality to add an entry via DOI + responsive error messages if resolving of DOI don't work + keep changes to choosen file to minimum
-rw-r--r--.gitignore4
-rw-r--r--Cargo.lock531
-rw-r--r--Cargo.toml3
-rw-r--r--README.md2
-rw-r--r--src/app.rs116
-rw-r--r--src/bibiman.rs288
-rw-r--r--src/main.rs4
-rw-r--r--src/tui/commands.rs10
-rw-r--r--src/tui/popup.rs10
-rw-r--r--src/tui/ui.rs59
-rw-r--r--tests/biblatex-test.bib2
-rw-r--r--tests/multi-files/bibfile1.bib14
12 files changed, 969 insertions, 74 deletions
diff --git a/.gitignore b/.gitignore
index 8bac1c1..ba75650 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,7 @@
**/*.tape
res/*.gif
res/*.png
+.direnv
+.devbox
+.envrc
+devbox.json
diff --git a/Cargo.lock b/Cargo.lock
index a6c926b..ddd8f7c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "addr2line"
@@ -24,6 +24,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -70,6 +79,12 @@ dependencies = [
]
[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
name = "bibiman"
version = "0.8.2"
dependencies = [
@@ -83,11 +98,14 @@ dependencies = [
"itertools",
"lexopt",
"nucleo-matcher",
+ "rand",
"ratatui",
+ "regex",
"signal-hook",
"tokio",
"tokio-util",
"tui-input",
+ "ureq",
"walkdir",
]
@@ -132,6 +150,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -337,6 +361,17 @@ dependencies = [
]
[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "dlib"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -469,6 +504,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -617,6 +661,145 @@ dependencies = [
]
[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
name = "image"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -727,6 +910,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
+name = "litemap"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
+
+[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1008,6 +1197,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
name = "petgraph"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1049,6 +1244,15 @@ dependencies = [
]
[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1076,6 +1280,36 @@ dependencies = [
]
[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
name = "ratatui"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1117,6 +1351,50 @@ dependencies = [
]
[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1136,6 +1414,38 @@ dependencies = [
]
[[package]]
+name = "rustls"
+version = "0.23.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1"
+dependencies = [
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
name = "rustversion"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1169,6 +1479,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
+name = "serde"
+version = "1.0.210"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.210"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1251,6 +1581,18 @@ dependencies = [
]
[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1279,6 +1621,12 @@ dependencies = [
]
[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
name = "syn"
version = "2.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1290,6 +1638,17 @@ dependencies = [
]
[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "tempfile"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1344,6 +1703,16 @@ dependencies = [
]
[[package]]
+name = "tinystr"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
name = "tinyvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1516,6 +1885,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47"
[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "ureq"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
+dependencies = [
+ "base64",
+ "flate2",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-pki-types",
+ "url",
+ "webpki-roots",
+]
+
+[[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1611,6 +2025,15 @@ dependencies = [
]
[[package]]
+name = "webpki-roots"
+version = "0.26.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
name = "weezl"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1816,6 +2239,18 @@ dependencies = [
]
[[package]]
+name = "write16"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
+[[package]]
name = "x11rb"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1831,3 +2266,97 @@ name = "x11rb-protocol"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
+
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/Cargo.toml b/Cargo.toml
index c0952c0..ca1f146 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,6 +24,7 @@ dirs = "5.0.1"
# doi = "0.3.0"
editor-command = "0.1.1"
futures = "0.3.30"
+rand = "0.8"
itertools = "0.13.0"
lexopt = "0.3.0"
nucleo-matcher = "0.3.1"
@@ -33,3 +34,5 @@ tokio = { version = "1.39.3", features = ["full"] }
tokio-util = "0.7.12"
tui-input = "0.11.0"
walkdir = "2.5.0"
+regex = "1.11.1"
+ureq = "2.12.1"
diff --git a/README.md b/README.md
index bf1749e..069b6d4 100644
--- a/README.md
+++ b/README.md
@@ -116,8 +116,8 @@ my personal workflow. There are more to come, the list will be updated:
- [x] **Scrollbar** for better navigating.
- [x] **Sort Entries** by column (`Authors`, `Title`, `Year`, `Pubtype`)
- [x] **Load multiple files** into one session.
+- [x] **Add Entry via DOI** as formatted code.
- [ ] **Open related notes file** for specific entry.
-- [ ] **Add Entry via DOI** as formatted code.
- [ ] **Implement config file** for setting some default values like main
bibfile, PDF-opener, or editor
- [ ] **Support Hayagriva(`.yaml`)** format as input (_on hold for now_, because
diff --git a/src/app.rs b/src/app.rs
index 54d42b6..2240e8f 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -23,6 +23,7 @@ use crate::tui::commands::InputCmdAction;
use crate::tui::popup::PopupKind;
use crate::tui::{self, Tui};
use crate::{bibiman::Bibiman, tui::commands::CmdAction};
+use core::panic;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::process::{Command, Stdio};
@@ -58,7 +59,7 @@ impl App {
})
}
- pub async fn run(&mut self, args: &CLIArgs) -> Result<()> {
+ pub async fn run(&mut self, args: &mut CLIArgs) -> Result<()> {
let mut tui = tui::Tui::new()?;
tui.enter()?;
@@ -109,29 +110,61 @@ impl App {
self.running = false;
}
- pub fn run_command(&mut self, cmd: CmdAction, args: &CLIArgs, tui: &mut Tui) -> Result<()> {
+ pub fn run_command(&mut self, cmd: CmdAction, args: &mut CLIArgs, tui: &mut Tui) -> Result<()> {
match cmd {
CmdAction::Input(cmd) => match cmd {
InputCmdAction::Nothing => {}
InputCmdAction::Handle(event) => {
self.input.handle_event(&event);
- self.bibiman.search_list_by_pattern(&self.input);
+ if let CurrentArea::SearchArea = self.bibiman.current_area {
+ self.bibiman.search_list_by_pattern(&self.input);
+ }
}
InputCmdAction::Enter => {
self.input_mode = true;
// Logic for TABS to be added
- self.bibiman.enter_search_area();
+ // self.bibiman.enter_search_area();
}
InputCmdAction::Confirm => {
+ // Logic for TABS to be added
+ if let CurrentArea::SearchArea = self.bibiman.current_area {
+ self.bibiman.confirm_search();
+ } else if let CurrentArea::PopupArea = self.bibiman.current_area {
+ match self.bibiman.popup_area.popup_kind {
+ Some(PopupKind::AddEntry) => {
+ let doi = self.input.value();
+ self.bibiman.close_popup();
+ self.input_mode = false;
+ // Check if the DOI pattern is valid. If not, show warning and break
+ if doi.starts_with("10.")
+ || doi.starts_with("https://doi.org")
+ || doi.starts_with("https://dx.doi.org")
+ || doi.starts_with("http://doi.org")
+ || doi.starts_with("http://dx.doi.org")
+ {
+ self.bibiman.handle_new_entry_submission(args, doi);
+ } else {
+ self.bibiman.popup_area.popup_message(
+ "No valid DOI pattern: ",
+ doi,
+ false,
+ );
+ }
+ }
+ _ => {}
+ }
+ }
self.input = Input::default();
self.input_mode = false;
- // Logic for TABS to be added
- self.bibiman.confirm_search();
}
InputCmdAction::Exit => {
self.input = Input::default();
self.input_mode = false;
- self.bibiman.break_search();
+ if let CurrentArea::SearchArea = self.bibiman.current_area {
+ self.bibiman.break_search();
+ } else if let CurrentArea::PopupArea = self.bibiman.current_area {
+ self.bibiman.close_popup();
+ }
}
},
CmdAction::SelectNextRow(amount) => match self.bibiman.current_area {
@@ -142,13 +175,15 @@ impl App {
CurrentArea::TagArea => {
self.bibiman.select_next_tag(amount);
}
- CurrentArea::PopupArea => {
- if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind {
+ CurrentArea::PopupArea => match self.bibiman.popup_area.popup_kind {
+ Some(PopupKind::Help) => {
self.bibiman.popup_area.popup_scroll_down();
- } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind {
+ }
+ Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) => {
self.bibiman.popup_area.popup_state.scroll_down_by(1)
}
- }
+ _ => {}
+ },
_ => {}
},
CmdAction::SelectPrevRow(amount) => match self.bibiman.current_area {
@@ -159,13 +194,15 @@ impl App {
CurrentArea::TagArea => {
self.bibiman.select_previous_tag(amount);
}
- CurrentArea::PopupArea => {
- if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind {
+ CurrentArea::PopupArea => match self.bibiman.popup_area.popup_kind {
+ Some(PopupKind::Help) => {
self.bibiman.popup_area.popup_scroll_up();
- } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind {
+ }
+ Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) => {
self.bibiman.popup_area.popup_state.scroll_up_by(1)
}
- }
+ _ => {}
+ },
_ => {}
},
CmdAction::SelectNextCol => {
@@ -205,14 +242,20 @@ impl App {
CmdAction::ToggleArea => {
self.bibiman.toggle_area();
}
- CmdAction::SearchList => {}
+ CmdAction::SearchList => {
+ self.input_mode = true;
+ self.bibiman.enter_search_area();
+ }
CmdAction::Reset => {
if let CurrentArea::PopupArea = self.bibiman.current_area {
if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind {
self.bibiman.popup_area.popup_scroll_pos = 0;
self.bibiman.close_popup()
- } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind {
+ } else if let Some(PopupKind::OpenRes) = self.bibiman.popup_area.popup_kind {
self.bibiman.close_popup()
+ } else if let Some(PopupKind::AppendToFile) = self.bibiman.popup_area.popup_kind
+ {
+ self.bibiman.close_popup();
}
} else {
self.bibiman.reset_current_list();
@@ -224,33 +267,11 @@ impl App {
} else if let CurrentArea::PopupArea = self.bibiman.current_area {
if let Some(PopupKind::Help) = self.bibiman.popup_area.popup_kind {
self.bibiman.close_popup();
- } else if let Some(PopupKind::Selection) = self.bibiman.popup_area.popup_kind {
- // Index of selected entry
- let entry_idx = self
- .bibiman
- .entry_table
- .entry_table_state
- .selected()
- .unwrap();
-
- // Index of selected popup field
- let popup_idx = self.bibiman.popup_area.popup_state.selected().unwrap();
-
- // Choose ressource depending an selected popup field
- if self.bibiman.popup_area.popup_list[popup_idx].contains("Weblink") {
- let object =
- self.bibiman.entry_table.entry_table_items[entry_idx].doi_url();
- let url = prepare_weblink(object);
- open_connected_link(&url)?;
- } else if self.bibiman.popup_area.popup_list[popup_idx].contains("File") {
- let object =
- self.bibiman.entry_table.entry_table_items[entry_idx].filepath();
- open_connected_file(object)?;
- } else {
- eprintln!("Unable to find ressource to open");
- };
- // run command to open file/Url
- self.bibiman.close_popup()
+ } else if let Some(PopupKind::OpenRes) = self.bibiman.popup_area.popup_kind {
+ self.bibiman.open_connected_res()?;
+ } else if let Some(PopupKind::AppendToFile) = self.bibiman.popup_area.popup_kind
+ {
+ self.bibiman.append_entry_to_file(args)?
}
}
}
@@ -299,6 +320,7 @@ impl App {
if entry.filepath.is_some() {
items.push("File (PDF/EPUB)".to_owned())
}
+ self.bibiman.popup_area.popup_kind = Some(PopupKind::OpenRes);
self.bibiman.popup_area.popup_selection(items);
self.bibiman.former_area = Some(FormerArea::EntryArea);
self.bibiman.current_area = CurrentArea::PopupArea;
@@ -312,6 +334,12 @@ impl App {
}
}
}
+ CmdAction::AddEntry => {
+ if let CurrentArea::EntryArea = self.bibiman.current_area {
+ self.input_mode = true;
+ self.bibiman.add_entry();
+ }
+ }
CmdAction::ShowHelp => {
self.bibiman.show_help();
}
diff --git a/src/bibiman.rs b/src/bibiman.rs
index bba3eec..0c0d99b 100644
--- a/src/bibiman.rs
+++ b/src/bibiman.rs
@@ -15,6 +15,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/////
+use crate::app;
use crate::bibiman::entries::EntryTableColumn;
use crate::bibiman::{bibisetup::*, search::BibiSearch};
use crate::cliargs::CLIArgs;
@@ -22,11 +23,17 @@ use crate::tui::popup::{PopupArea, PopupKind};
use crate::tui::Tui;
use crate::{bibiman::entries::EntryTable, bibiman::keywords::TagList};
use arboard::Clipboard;
-use color_eyre::eyre::{Ok, Result};
+use color_eyre::eyre::Result;
use editor_command::EditorBuilder;
+use futures::executor::block_on;
use ratatui::widgets::ScrollbarState;
+use regex::Regex;
use std::fs;
+use std::fs::{File, OpenOptions};
+use std::io::Write;
+use std::path::PathBuf;
use std::process::Command;
+use std::result::Result::Ok;
use tui_input::Input;
pub mod bibisetup;
@@ -107,6 +114,48 @@ impl Bibiman {
self.popup_area.popup_kind = Some(PopupKind::Help);
}
+ pub fn add_entry(&mut self) {
+ if let CurrentArea::EntryArea = self.current_area {
+ self.former_area = Some(FormerArea::EntryArea);
+ }
+ self.popup_area.is_popup = true;
+ self.current_area = CurrentArea::PopupArea;
+ self.popup_area.popup_kind = Some(PopupKind::AddEntry);
+ }
+
+ ///Try to resolve entered DOI. If successfull, choose file where to append
+ ///the new entry via `append_to_file()` function. If not, show error popup
+ ///
+ ///The method needs two arguments: the CLIArgs struct and the `str` containing the DOI
+ pub fn handle_new_entry_submission(&mut self, args: &CLIArgs, doi_string: &str) {
+ let doi_string = if doi_string.starts_with("10.") {
+ "https://doi.org/".to_string() + doi_string
+ } else {
+ doi_string.to_owned()
+ };
+
+ // Send GET request to doi resolver
+ let doi_entry = ureq::get(&doi_string)
+ .set("Accept", "application/x-bibtex")
+ .call();
+
+ if let Ok(entry) = doi_entry {
+ // Save generated bibtex entry in structs field
+ let entry = entry
+ .into_string()
+ .expect("Couldn't parse fetched entry into string");
+ self.popup_area.popup_sel_item = entry;
+ self.popup_area.popup_kind = Some(PopupKind::AppendToFile);
+ self.append_to_file(args);
+ self.former_area = Some(FormerArea::EntryArea);
+ self.current_area = CurrentArea::PopupArea;
+ self.popup_area.popup_state.select(Some(0))
+ } else {
+ self.popup_area
+ .popup_message("Can't find DOI: ", &doi_string, false);
+ }
+ }
+
pub fn close_popup(&mut self) {
// Reset all popup fields to default values
self.popup_area = PopupArea::default();
@@ -299,6 +348,26 @@ impl Bibiman {
}
}
+ pub fn select_entry_by_citekey(&mut self, citekey: &str) {
+ // Search for entry by matching citekeys
+ let mut idx_count = 0;
+ loop {
+ if idx_count == self.entry_table.entry_table_items.len() {
+ idx_count = 0;
+ break;
+ } else if self.entry_table.entry_table_items[idx_count]
+ .citekey
+ .contains(citekey)
+ {
+ break;
+ }
+ idx_count += 1
+ }
+
+ // Set selected entry to vec-index of match
+ self.entry_table.entry_table_state.select(Some(idx_count));
+ }
+
pub fn run_editor(&mut self, args: &CLIArgs, tui: &mut Tui) -> Result<()> {
// get filecontent and citekey for calculating line number
let citekey: &str = &self.entry_table.entry_table_items
@@ -367,25 +436,205 @@ impl Bibiman {
// Update the database and the lists to show changes
Self::update_lists(self, args);
- // Search for entry, selected before editing, by matching citekeys
- // Use earlier saved copy of citekey to match
- let mut idx_count = 0;
- loop {
- if self.entry_table.entry_table_items[idx_count]
- .citekey
- .contains(citekey)
- {
- break;
+ // Select entry which was selected before entering editor
+ self.select_entry_by_citekey(citekey);
+
+ Ok(())
+ }
+
+ pub fn append_to_file(&mut self, args: &CLIArgs) {
+ let mut items = vec!["Create new file".to_owned()];
+ if args.files.len() > 1 {
+ for f in args.files.clone() {
+ items.push(f.to_str().unwrap().to_owned());
}
- idx_count += 1
+ } else {
+ items.push(args.files.first().unwrap().to_str().unwrap().to_owned());
}
+ self.popup_area.popup_selection(items);
+ }
- // Set selected entry to vec-index of match
- self.entry_table.entry_table_state.select(Some(idx_count));
+ pub fn append_entry_to_file(&mut self, args: &mut CLIArgs) -> Result<()> {
+ // Index of selected popup field
+ let popup_idx = self.popup_area.popup_state.selected().unwrap();
+
+ // regex pattern to match citekey in fetched bibtexstring
+ let pattern = Regex::new(r"\{([^\{\},]*),").unwrap();
+
+ let citekey = pattern
+ .captures(&self.popup_area.popup_sel_item)
+ .unwrap()
+ .get(1)
+ .unwrap()
+ .as_str()
+ .to_string();
+
+ // Check if new file or existing file was choosen
+ let mut file = if self.popup_area.popup_list[popup_idx].contains("Create new file") {
+ let citekey = PathBuf::from(&citekey);
+ // Get path of current files
+ let path: PathBuf = if args.files[0].is_file() {
+ args.files[0].parent().unwrap().to_owned()
+ } else {
+ dirs::home_dir().unwrap() // home dir as fallback
+ };
+
+ let citekey = citekey.with_extension("bib");
+
+ let newfile = path.join(citekey);
+
+ args.files.push(newfile.clone());
+
+ File::create_new(newfile).unwrap()
+ } else {
+ let file_path = &args.files[popup_idx - 1];
+ OpenOptions::new().append(true).open(file_path).unwrap()
+ };
+ // Optionally, add a newline before the content
+ file.write_all(b"\n")?;
+ // Write content to file
+ file.write_all(self.popup_area.popup_sel_item.as_bytes())?;
+ // Update the database and the lists to reflect the new content
+ self.update_lists(args);
+ self.close_popup();
+
+ // Select newly created entry
+ self.select_entry_by_citekey(&citekey);
+
+ Ok(())
+ }
+
+ pub fn open_connected_res(&mut self) -> Result<()> {
+ // Index of selected entry
+ let entry_idx = self.entry_table.entry_table_state.selected().unwrap();
+
+ // Index of selected popup field
+ let popup_idx = self.popup_area.popup_state.selected().unwrap();
+
+ // Choose ressource depending an selected popup field
+ if self.popup_area.popup_list[popup_idx].contains("Weblink") {
+ let object = self.entry_table.entry_table_items[entry_idx].doi_url();
+ let url = app::prepare_weblink(object);
+ app::open_connected_link(&url)?;
+ } else if self.popup_area.popup_list[popup_idx].contains("File") {
+ let object = self.entry_table.entry_table_items[entry_idx].filepath();
+ app::open_connected_file(object)?;
+ } else {
+ eprintln!("Unable to find ressource to open");
+ };
+ // run command to open file/Url
+ self.close_popup();
Ok(())
}
+ /// Formats a raw BibTeX entry string for better readability.
+ pub fn format_bibtex_entry(entry: &str, file_path: &str) -> String {
+ let mut formatted = String::new();
+
+ // Find the position of the first '{'
+ if let Some(start_brace_pos) = entry.find('{') {
+ // Extract the preamble (e.g., '@article{')
+ let preamble = &entry[..start_brace_pos + 1];
+ let preamble = preamble.trim_start();
+ formatted.push_str(preamble);
+ // formatted.push('\n'); // Add newline
+
+ // Get the content inside the braces
+ let rest = &entry[start_brace_pos + 1..];
+ // Remove the last '}' at the end, if present
+ let rest = rest.trim_end();
+ let rest = if rest.ends_with('}') {
+ &rest[..rest.len() - 1]
+ } else {
+ rest
+ };
+
+ // Parse the fields, considering braces and quotes
+ let mut fields = Vec::new();
+ let mut current_field = String::new();
+ let mut brace_level = 0;
+ let mut in_quotes = false;
+ for c in rest.chars() {
+ match c {
+ '{' if !in_quotes => {
+ brace_level += 1;
+ current_field.push(c);
+ }
+ '}' if !in_quotes => {
+ brace_level -= 1;
+ current_field.push(c);
+ }
+ '"' => {
+ in_quotes = !in_quotes;
+ current_field.push(c);
+ }
+ ',' if brace_level == 0 && !in_quotes => {
+ // Outside of braces and quotes, comma separates fields
+ fields.push(current_field.trim().to_string());
+ current_field.clear();
+ }
+ _ => {
+ current_field.push(c);
+ }
+ }
+ }
+ // Add the last field
+ if !current_field.trim().is_empty() {
+ fields.push(current_field.trim().to_string());
+ }
+
+ // **Conditionally Clean the Citation Key**
+ if let Some(citation_key) = fields.get_mut(0) {
+ // Check if the citation key contains any non-alphanumerical characters except underscores
+ let needs_cleaning = citation_key
+ .chars()
+ .any(|c| !c.is_alphanumeric() && c != '_');
+ if needs_cleaning {
+ // Retain only alphanumerical characters and underscores
+ let cleaned_key: String = citation_key
+ .chars()
+ .filter(|c| c.is_alphanumeric() || *c == '_')
+ .collect();
+ // If the cleaned key is longer than 14 characters, retain only the last 14
+ let limited_key = if cleaned_key.len() > 14 {
+ cleaned_key
+ .chars()
+ .rev()
+ .take(14)
+ .collect::<String>()
+ .chars()
+ .rev()
+ .collect()
+ } else {
+ cleaned_key
+ };
+ // Replace the original citation key with the cleaned and possibly limited key
+ *citation_key = limited_key;
+ }
+ }
+
+ // Add the new 'file' field
+ let file_field = format!("file = {{{}}}", file_path);
+ fields.push(file_field);
+
+ // Reconstruct the entry with proper indentation
+ for (i, field) in fields.iter().enumerate() {
+ formatted.push_str(" ");
+ formatted.push_str(field);
+ // Add a comma if it's not the last field
+ if i < fields.len() - 1 {
+ formatted.push(',');
+ }
+ formatted.push('\n');
+ }
+ formatted.push('}'); // Close the entry
+ formatted
+ } else {
+ // No opening brace found, return the entry as is
+ entry.to_string()
+ }
+ }
// Search entry list
pub fn search_entries(&mut self) {
// Use snapshot of entry list saved when starting the search
@@ -594,7 +843,7 @@ impl Bibiman {
#[cfg(test)]
mod tests {
- // use super::*;
+ use super::*;
#[test]
fn citekey_pattern() {
@@ -602,4 +851,15 @@ mod tests {
assert_eq!(citekey, "{a_key_2001,")
}
+
+ #[test]
+ fn regex_capture_citekey() {
+ let re = Regex::new(r"\{([^\{\},]*),").unwrap();
+
+ let bibstring = String::from("@article{citekey77_2001:!?, author = {Hanks, Tom}, title = {A great book}, year = {2001}}");
+
+ let result = re.captures(&bibstring).unwrap();
+
+ assert_eq!(result.get(1).unwrap().as_str(), "citekey77_2001:!?")
+ }
}
diff --git a/src/main.rs b/src/main.rs
index b1160e2..78c5075 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -29,7 +29,7 @@ pub mod tui;
#[tokio::main]
async fn main() -> Result<()> {
// Parse CLI arguments
- let parsed_args = CLIArgs::parse_args().unwrap();
+ let mut parsed_args = CLIArgs::parse_args().unwrap();
// Print help if -h/--help flag is passed and exit
if parsed_args.helparg {
@@ -48,6 +48,6 @@ async fn main() -> Result<()> {
// Create an application.
let mut app = App::new(&parsed_args)?;
- app.run(&parsed_args).await?;
+ app.run(&mut parsed_args).await?;
Ok(())
}
diff --git a/src/tui/commands.rs b/src/tui/commands.rs
index a3049ee..0e00f95 100644
--- a/src/tui/commands.rs
+++ b/src/tui/commands.rs
@@ -69,6 +69,8 @@ pub enum CmdAction {
Exit,
// Show keybindings
ShowHelp,
+ // Add new entry
+ AddEntry,
// Do nothing.
Nothing,
}
@@ -110,7 +112,6 @@ impl From<KeyEvent> for CmdAction {
Self::SelectPrevRow(5)
} else {
Self::Nothing
- // Self::Open(OpenRessource::WebLink)
}
}
// Scroll info/preview area
@@ -118,6 +119,8 @@ impl From<KeyEvent> for CmdAction {
KeyCode::PageUp => Self::ScrollInfoUp,
// Exit App
KeyCode::Char('q') => Self::Exit,
+ // Add new entry
+ KeyCode::Char('a') => Self::AddEntry,
KeyCode::Char('c') | KeyCode::Char('C') => {
if key_event.modifiers == KeyModifiers::CONTROL {
Self::Exit
@@ -129,15 +132,14 @@ impl From<KeyEvent> for CmdAction {
KeyCode::Tab => Self::ToggleArea,
KeyCode::BackTab => Self::ToggleArea,
// Enter search mode
- KeyCode::Char('/') => Self::Input(InputCmdAction::Enter),
+ KeyCode::Char('/') => Self::SearchList,
KeyCode::Char('f') => {
if key_event.modifiers == KeyModifiers::CONTROL {
- Self::Input(InputCmdAction::Enter)
+ Self::SearchList
} else {
Self::Nothing
}
}
- // KeyCode::Backspace => Self::Input(InputCommand::Resume(Event::Key(key_event))),
// Confirm selection
KeyCode::Enter => Self::Confirm,
// Reset lists/tables
diff --git a/src/tui/popup.rs b/src/tui/popup.rs
index 890e5c8..78a0719 100644
--- a/src/tui/popup.rs
+++ b/src/tui/popup.rs
@@ -28,7 +28,9 @@ pub enum PopupKind {
Help,
MessageConfirm,
MessageError,
- Selection,
+ OpenRes,
+ AppendToFile,
+ AddEntry,
}
#[derive(Debug, Default)]
@@ -39,6 +41,9 @@ pub struct PopupArea {
pub popup_scroll_pos: u16,
pub popup_list: Vec<String>,
pub popup_state: ListState,
+ pub popup_sel_item: String,
+ // pub add_entry_input: String,
+ // pub add_entry_cursor_position: usize,
}
impl PopupArea {
@@ -60,6 +65,7 @@ impl PopupArea {
("e: ", "Open editor at selected entry"),
("o: ", "Open with selected entry associated PDF"),
("u: ", "Open DOI/URL of selected entry"),
+ ("a: ", "Add new entry"),
("ESC: ", "Reset all lists"),
("Keyword List", "sub"),
("j,k|↓,↑: ", "Select next/previous item"),
@@ -119,7 +125,7 @@ impl PopupArea {
pub fn popup_selection(&mut self, items: Vec<String>) {
self.popup_list = items;
- self.popup_kind = Some(PopupKind::Selection);
+ // self.popup_kind = Some(PopupKind::SelectRes);
self.is_popup = true;
}
diff --git a/src/tui/ui.rs b/src/tui/ui.rs
index cca87ce..4f64338 100644
--- a/src/tui/ui.rs
+++ b/src/tui/ui.rs
@@ -181,6 +181,46 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) {
frame.render_widget(Clear, popup_area);
frame.render_widget(par, popup_area)
}
+
+ Some(PopupKind::AddEntry) => {
+ let area = frame.area();
+
+ let block = Block::bordered()
+ .title_top(" Add Entry ".bold())
+ .title_bottom(" (ESC) ━ (ENTER) ".bold())
+ .title_alignment(Alignment::Center)
+ .style(
+ Style::new()
+ .fg(Color::Indexed(args.colors.main_text_color))
+ .bg(Color::Indexed(args.colors.popup_bg_color)),
+ )
+ .border_set(symbols::border::THICK)
+ .border_style(Style::new().fg(Color::Indexed(args.colors.entry_color)));
+
+ // Prepare the input fields
+ let content = vec![Line::from(vec![
+ Span::styled(
+ "DOI: ",
+ Style::new().fg(Color::Indexed(args.colors.entry_color)),
+ ),
+ Span::raw(app.input.value().to_string().clone()),
+ ])];
+ let paragraph = Paragraph::new(content)
+ .block(block.clone())
+ .style(Style::new().fg(Color::Indexed(args.colors.main_text_color)))
+ .wrap(Wrap { trim: false });
+
+ let doi_lines = paragraph.line_count(area.width / 2);
+ // Calculate popup size
+ let popup_width = area.width / 4 * 3;
+ let popup_height = doi_lines as u16; // Adjust as needed
+ let popup_area = popup_area(area, popup_width, popup_height);
+
+ // Render the popup
+ frame.render_widget(Clear, popup_area);
+ render_cursor(app, frame, popup_area, 6, doi_lines as u16 - 1);
+ frame.render_widget(paragraph, popup_area);
+ }
Some(PopupKind::MessageConfirm) => {
let area = frame.area();
@@ -243,7 +283,7 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) {
frame.render_widget(Clear, popup_area);
frame.render_widget(&content, popup_area)
}
- Some(PopupKind::Selection) => {
+ Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) => {
let list_items: Vec<ListItem> = app
.bibiman
.popup_area
@@ -252,8 +292,16 @@ pub fn render_popup(app: &mut App, args: &CLIArgs, frame: &mut Frame) {
.map(|item| ListItem::from(item.to_owned()))
.collect();
+ let title = if let Some(PopupKind::OpenRes) = app.bibiman.popup_area.popup_kind {
+ " Open "
+ } else if let Some(PopupKind::AppendToFile) = app.bibiman.popup_area.popup_kind {
+ " Select file to append entry "
+ } else {
+ " Select "
+ };
+
let block = Block::bordered()
- .title_top(" Open ".bold())
+ .title_top(title.bold())
.title_bottom(" (j,k|↓,↑) ━ (ENTER) ━ (ESC) ".bold())
.title_alignment(Alignment::Center)
.style(
@@ -327,7 +375,7 @@ pub fn render_footer(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Rec
]))
.block(block);
- render_cursor(app, frame, rect, title_lenght + 1);
+ render_cursor(app, frame, rect, title_lenght + 1, 1);
frame.render_widget(search_string, rect);
}
@@ -986,12 +1034,13 @@ pub fn render_taglist(app: &mut App, args: &CLIArgs, frame: &mut Frame, rect: Re
}
/// Render the cursor when in InputMode
-fn render_cursor(app: &mut App, frame: &mut Frame, area: Rect, x_offset: u16) {
+fn render_cursor(app: &mut App, frame: &mut Frame, area: Rect, x_offset: u16, y_offset: u16) {
let scroll = app.input.visual_scroll(area.width as usize);
if app.input_mode {
let (x, y) = (
area.x + ((app.input.visual_cursor()).max(scroll) - scroll) as u16 + x_offset,
- area.bottom().saturating_sub(1),
+ // area.bottom().saturating_sub(1) + y_offset,
+ area.bottom().saturating_sub(y_offset),
);
frame.render_widget(
Clear,
diff --git a/tests/biblatex-test.bib b/tests/biblatex-test.bib
index b366fc3..d0fc0a6 100644
--- a/tests/biblatex-test.bib
+++ b/tests/biblatex-test.bib
@@ -65,7 +65,7 @@
author = {Aristotle},
location = {New York},
publisher = {G. P. Putnam},
- url = {infobooks.org/authors/classic/aristotle-books/#Physic},
+ url = {https://www.infobooks.org/authors/classic/aristotle-books/#Physic},
date = {1929},
translator = {Wicksteed, P. H. and Cornford, F. M.},
keywords = {primary, ancient, philosophy},
diff --git a/tests/multi-files/bibfile1.bib b/tests/multi-files/bibfile1.bib
index 31d81bc..230a517 100644
--- a/tests/multi-files/bibfile1.bib
+++ b/tests/multi-files/bibfile1.bib
@@ -11,3 +11,17 @@
langidopts = {variant=american},
annotation = {A \texttt{collection} entry providing the excerpt information for the \texttt{doody} entry. Note the format of the \texttt{pages} field},
}
+
+@book{
+ Bernal_2001,
+ title={Black Athena Writes Back: Martin Bernal Responds to His Critics},
+ ISBN={9780822380078},
+ url={http://dx.doi.org/10.1515/9780822380078},
+ DOI={10.1515/9780822380078},
+ publisher={Duke University Press},
+ author={Bernal, Martin},
+ editor={Moore, David Chioni},
+ year={2001},
+ month=sep,
+ file = {}
+} \ No newline at end of file