diff options
| author | lukeflo | 2024-12-23 21:03:48 +0100 |
|---|---|---|
| committer | lukeflo | 2024-12-23 21:03:48 +0100 |
| commit | 57126d7f42b871aa1835b056fbe74179c13e53b0 (patch) | |
| tree | bf81db50446aa7a8e06553f4e8aff97ea0cfb816 | |
| parent | ad5c2cb586616eca99fc1db0efaaa0ff5aa97144 (diff) | |
| parent | 9a33a794167d60ce35030f007674f6e9424b1ff3 (diff) | |
| download | bibiman-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-- | .gitignore | 4 | ||||
| -rw-r--r-- | Cargo.lock | 531 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | src/app.rs | 116 | ||||
| -rw-r--r-- | src/bibiman.rs | 288 | ||||
| -rw-r--r-- | src/main.rs | 4 | ||||
| -rw-r--r-- | src/tui/commands.rs | 10 | ||||
| -rw-r--r-- | src/tui/popup.rs | 10 | ||||
| -rw-r--r-- | src/tui/ui.rs | 59 | ||||
| -rw-r--r-- | tests/biblatex-test.bib | 2 | ||||
| -rw-r--r-- | tests/multi-files/bibfile1.bib | 14 |
12 files changed, 969 insertions, 74 deletions
@@ -6,3 +6,7 @@ **/*.tape res/*.gif res/*.png +.direnv +.devbox +.envrc +devbox.json @@ -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", +] @@ -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" @@ -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 @@ -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 |
