diff options
| author | lukeflo | 2024-09-20 22:31:33 +0200 |
|---|---|---|
| committer | lukeflo | 2024-09-20 22:31:33 +0200 |
| commit | dc45b960a4eda299058e597f6867e4d4be109b1b (patch) | |
| tree | 30501aa9f1f26413f959d751302fa189508eacc1 | |
| download | bibiman-dc45b960a4eda299058e597f6867e4d4be109b1b.tar.gz bibiman-dc45b960a4eda299058e597f6867e4d4be109b1b.zip | |
initial commit
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Cargo.lock | 960 | ||||
| -rw-r--r-- | Cargo.toml | 15 | ||||
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | plainfile | 3 | ||||
| -rw-r--r-- | src/backend.rs | 2 | ||||
| -rw-r--r-- | src/backend/bib.rs | 51 | ||||
| -rw-r--r-- | src/backend/cliargs.rs | 89 | ||||
| -rw-r--r-- | src/frontend.rs | 5 | ||||
| -rw-r--r-- | src/frontend/app.rs | 221 | ||||
| -rw-r--r-- | src/frontend/event.rs | 97 | ||||
| -rw-r--r-- | src/frontend/handler.rs | 70 | ||||
| -rw-r--r-- | src/frontend/tui.rs | 77 | ||||
| -rw-r--r-- | src/frontend/ui.rs | 201 | ||||
| -rw-r--r-- | src/main.rs | 67 | ||||
| -rw-r--r-- | test.bib | 48 |
16 files changed, 1910 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d0d331 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/src*.bak +todolist.md diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f579ee9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,960 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bibiman" +version = "0.1.0" +dependencies = [ + "biblatex", + "crossterm", + "futures", + "ratatui", + "regex", + "sarge", + "tokio", +] + +[[package]] +name = "biblatex" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27fe7285040d0227cd8b5395e1c4783f44f0b673eca5a657f4432ae401f2b7b8" +dependencies = [ + "numerals", + "paste", + "strum", + "unicode-normalization", + "unscanny", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.4.2", + "crossterm_winapi", + "futures-core", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "instability" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lru" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "numerals" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25be21376a772d15f97ae789845340a9651d3c4246ff5ebb6a2b35f9c37bd31" + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +dependencies = [ + "bitflags 2.4.2", + "cassowary", + "compact_str", + "crossterm", + "instability", + "itertools", + "lru", + "paste", + "strum", + "strum_macros", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "sarge" +version = "7.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2096e40214337b2293fa4c3eb63c5376b57f4247cbbaf6d0af477f1d36ac21d9" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unscanny" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8584b8f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bibiman" +version = "0.1.0" +authors = ["lukeflo <lukeflo@some.email.not>"] +license = "GPL-3.0-or-later" +edition = "2021" + +[dependencies] +biblatex = "0.9.3" +crossterm = { version = "0.28.1", features = ["event-stream"] } +futures = "0.3.30" +ratatui = "0.28.1" +regex = "1.10.6" +sarge = "7.2.5" +tokio = { version = "1.39.3", features = ["full"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c22348 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Bibiman diff --git a/plainfile b/plainfile new file mode 100644 index 0000000..78481bb --- /dev/null +++ b/plainfile @@ -0,0 +1,3 @@ +My first entry +A second entry +And a third one diff --git a/src/backend.rs b/src/backend.rs new file mode 100644 index 0000000..7907145 --- /dev/null +++ b/src/backend.rs @@ -0,0 +1,2 @@ +pub mod bib; +pub mod cliargs; diff --git a/src/backend/bib.rs b/src/backend/bib.rs new file mode 100644 index 0000000..aa7272f --- /dev/null +++ b/src/backend/bib.rs @@ -0,0 +1,51 @@ +use biblatex::Bibliography; +use regex::Regex; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use super::cliargs::{CLIArgs, PosArgs}; + +// Set necessary fields +// TODO: can surely be made more efficient/simpler +pub struct Bibi { + pub citekeys: Vec<String>, + // pub bibliography: Bibliography, +} + +pub fn get_bibfile(filename: impl AsRef<Path>) -> String { + let bibfile = fs::read_to_string(&filename).unwrap(); + bibfile +} + +pub fn get_citekeys(bibstring: &Bibliography) -> Vec<String> { + // let bib = Bibliography::parse(&get_bibfile(CLIArgs::parse_cli_args().bibfilearg)).unwrap(); + // // Define Regex to match citekeys + // let re = Regex::new(r"(?m)^\@[a-zA-Z]*\{(.*)\,").unwrap(); + // // Declare empty vector to fill with captured keys + // // Has to be Vec<&str> because of captures_iter method + // let mut keys = vec![]; + // for (_, [key]) in re.captures_iter(&bibfilestring).map(|c| c.extract()) { + // keys.push(key); + // } + // // Transform Vec<&str> to Vec<String> which is needed by the struct Bibi + // let mut citekeys: Vec<String> = keys.into_iter().map(String::from).collect(); + // // Sort vector items case-insensitive + // citekeys.sort_by_key(|name| name.to_lowercase()); + // citekeys + let mut citekeys: Vec<String> = bibstring.iter().map(|entry| entry.to_owned().key).collect(); + citekeys.sort_by_key(|name| name.to_lowercase()); + citekeys +} + +impl Bibi { + pub fn new() -> Self { + // TODO: Needs check for config file path as soon as config file is impl + let bib = Bibliography::parse(&get_bibfile(PosArgs::parse_pos_args().bibfilearg)).unwrap(); + Self { + citekeys: get_citekeys(&bib), + // bibliography: biblatex::Bibliography::parse(&bibfilestring).unwrap(), + } + } +} diff --git a/src/backend/cliargs.rs b/src/backend/cliargs.rs new file mode 100644 index 0000000..b820b6a --- /dev/null +++ b/src/backend/cliargs.rs @@ -0,0 +1,89 @@ +use core::panic; +use std::path::{Path, PathBuf}; + +use sarge::prelude::*; + +sarge! { + // Name of the struct + ArgumentsCLI, + + // Show help and exit. + 'h' help: bool, + + // Show version and exit. TODO: Write version... + 'v' version: bool, + + // Option for file: -b - short option; --bibfile - long option + // #ok makes it optional + #ok 'b' bibfile: String, +} + +// struct for CLIArgs +pub struct CLIArgs { + pub helparg: bool, + pub versionarg: bool, +} + +impl CLIArgs { + pub fn parse_cli_args() -> Self { + let (cli_args, _) = ArgumentsCLI::parse().expect("Could not parse CLI arguments"); + Self { + helparg: cli_args.help, + versionarg: cli_args.version, + } + } +} + +// Struct for positional arguments +// TODO: Can surely be improved!! +pub struct PosArgs { + pub bibfilearg: PathBuf, +} + +impl PosArgs { + pub fn parse_pos_args() -> Self { + let (_, pos_args) = ArgumentsCLI::parse().expect("Could not parse positional arguments"); + Self { + bibfilearg: if pos_args.len() > 1 { + PathBuf::from(&pos_args[1]) + // pos_args[1].to_string() + } else { + panic!("No path to bibfile provided as argument") + }, // bibfilearg: pos_args[1].to_string(), + } + } +} + +pub fn help_func() -> String { + let help = format!( + "\ +{} {} + +USAGE: + bibiman [FLAGS] [file] + +POSITIONAL ARGS: + <file> Path to .bib file + +FLAGS: + -h, --help Show this help and exit + -v, --version Show the version and exit", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + ); + help +} + +pub fn version_func() -> String { + let version = format!( + "\ +{} {} +{} +{}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_AUTHORS"), + env!("CARGO_PKG_LICENSE") + ); + version +} diff --git a/src/frontend.rs b/src/frontend.rs new file mode 100644 index 0000000..6ec15f2 --- /dev/null +++ b/src/frontend.rs @@ -0,0 +1,5 @@ +pub mod app; +pub mod event; +pub mod ui; +pub mod tui; +pub mod handler; diff --git a/src/frontend/app.rs b/src/frontend/app.rs new file mode 100644 index 0000000..ce250cd --- /dev/null +++ b/src/frontend/app.rs @@ -0,0 +1,221 @@ +use crate::backend::bib::*; +use std::error; + +use ratatui::widgets::ListState; + +// Application result type. +pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>; + +// Areas in which actions are possible +#[derive(Debug)] +pub enum CurrentArea { + EntryArea, + TagArea, + // SearchArea, +} + +// Application. +#[derive(Debug)] +pub struct App { + // Is the application running? + pub running: bool, + // list + pub tag_list: TagList, + // TODO: table items + pub entry_list: EntryList, + // area + pub current_area: CurrentArea, +} + +// Define the fundamental List +#[derive(Debug)] +pub struct TagList { + pub tag_list_items: Vec<TagListItem>, + pub tag_list_state: ListState, +} + +// Structure of the list items. Can be a simple string or something more elaborated +// eg: +// struct TagListItem { +// todo: String, +// info: String, +// status: Status, +// } +// where Status has to be defined explicitly somewhere else +#[derive(Debug)] +pub struct TagListItem { + pub info: String, +} + +// Function to process inputed characters and convert them (to string, or more complex function) +impl TagListItem { + pub fn new(info: &str) -> Self { + Self { + info: info.to_string(), + } + } +} + +// INFO: in the original template it was <&'static str> instead of <String> +impl FromIterator<String> for TagList { + // INFO: Here to originally <&'static str> + fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self { + let tag_list_items = iter + .into_iter() + // INFO: here originally not borrowed (without Ampersand'&') + .map(|info| TagListItem::new(&info)) + .collect(); + let tag_list_state = ListState::default(); // for preselection: .with_selected(Some(0)); + Self { + tag_list_items, + tag_list_state, + } + } +} + +impl FromIterator<(String, String)> for EntryList { + fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self { + let entry_list_items = iter + .into_iter() + .map(|(authors, title)| EntryListItem::new(&authors, &title)) + .collect(); + let entry_list_state = ListState::default(); + Self { + entry_list_items, + entry_list_state, + } + } +} + +// Define list containing entries as table +#[derive(Debug)] +pub struct EntryList { + pub entry_list_items: Vec<EntryListItem>, + pub entry_list_state: ListState, +} + +// Define contents of each entry table row +#[derive(Debug)] +pub struct EntryListItem { + pub authors: String, + pub title: String, + // pub year: u16, +} + +impl EntryListItem { + pub fn new(authors: &str, title: &str) -> Self { + Self { + authors: authors.to_string(), + title: title.to_string(), + } + } +} + +impl Default for App { + fn default() -> Self { + // TEST: read file + let lines = Bibi::new().citekeys; + let iter = vec![ + ( + "Mrs. Doubtfire".to_string(), + "A great book of great length".to_string(), + ), + ("Veye Tatah".to_string(), "Modern economy".to_string()), + ("Joseph Conrad".to_string(), "Heart of Darkness".to_string()), + ( + "Michelle-Rolpg Trouillot".to_string(), + "Silencing the Past".to_string(), + ), + ("Zora Neale Hurston".to_string(), "Barracoon".to_string()), + ]; + // let mylist = ["Item 1", "Item 2"]; + Self { + running: true, + // INFO: here the function(s) for creating the list has to be placed inside the parantheses -> Bib::whatever + tag_list: TagList::from_iter(lines), + entry_list: EntryList::from_iter(iter), + current_area: CurrentArea::EntryArea, + } + } +} + +impl App { + // Constructs a new instance of [`App`]. + pub fn new() -> Self { + Self::default() + } + + // Handles the tick event of the terminal. + pub fn tick(&self) {} + + // Set running to false to quit the application. + pub fn quit(&mut self) { + self.running = false; + } + + // Toggle moveable list between entries and tags + pub fn toggle_area(&mut self) { + match self.current_area { + CurrentArea::EntryArea => self.current_area = CurrentArea::TagArea, + CurrentArea::TagArea => self.current_area = CurrentArea::EntryArea, + } + } + + pub fn select_none(&mut self) { + match self.current_area { + CurrentArea::EntryArea => self.entry_list.entry_list_state.select(None), + CurrentArea::TagArea => self.tag_list.tag_list_state.select(None), + } + // self.tag_list.tag_list_state.select(None); + } + + pub fn select_next(&mut self) { + match self.current_area { + CurrentArea::EntryArea => self.entry_list.entry_list_state.select_next(), + CurrentArea::TagArea => self.tag_list.tag_list_state.select_next(), + } + // self.tag_list.tag_list_state.select_next(); + } + pub fn select_previous(&mut self) { + match self.current_area { + CurrentArea::EntryArea => self.entry_list.entry_list_state.select_previous(), + CurrentArea::TagArea => self.tag_list.tag_list_state.select_previous(), + } + // self.tag_list.tag_list_state.select_previous(); + } + + pub fn select_first(&mut self) { + match self.current_area { + CurrentArea::EntryArea => self.entry_list.entry_list_state.select_first(), + CurrentArea::TagArea => self.tag_list.tag_list_state.select_first(), + } + // self.tag_list.tag_list_state.select_first(); + } + + pub fn select_last(&mut self) { + match self.current_area { + CurrentArea::EntryArea => self.entry_list.entry_list_state.select_last(), + CurrentArea::TagArea => self.tag_list.tag_list_state.select_last(), + } + // self.tag_list.tag_list_state.select_last(); + } + + // pub fn select_none(&mut self) { + // self.entry_list.entry_list_state.select(None); + // } + + // pub fn select_next(&mut self) { + // self.entry_list.entry_list_state.select_next(); + // } + // pub fn select_previous(&mut self) { + // self.entry_list.entry_list_state.select_previous(); + // } + + // pub fn select_first(&mut self) { + // self.entry_list.entry_list_state.select_first(); + // } + + // pub fn select_last(&mut self) { + // self.entry_list.entry_list_state.select_last(); + // } +} diff --git a/src/frontend/event.rs b/src/frontend/event.rs new file mode 100644 index 0000000..f83dfea --- /dev/null +++ b/src/frontend/event.rs @@ -0,0 +1,97 @@ +use std::time::Duration; + +use crossterm::event::{Event as CrosstermEvent, KeyEvent, MouseEvent}; +use futures::{FutureExt, StreamExt}; +use tokio::sync::mpsc; + +use crate::frontend::app::AppResult; + +/// Terminal events. +#[derive(Clone, Copy, Debug)] +pub enum Event { + /// Terminal tick. + Tick, + /// Key press. + Key(KeyEvent), + /// Mouse click/scroll. + Mouse(MouseEvent), + /// Terminal resize. + Resize(u16, u16), +} + +/// Terminal event handler. +#[allow(dead_code)] +#[derive(Debug)] +pub struct EventHandler { + /// Event sender channel. + sender: mpsc::UnboundedSender<Event>, + /// Event receiver channel. + receiver: mpsc::UnboundedReceiver<Event>, + /// Event handler thread. + handler: tokio::task::JoinHandle<()>, +} + +impl EventHandler { + /// Constructs a new instance of [`EventHandler`]. + pub fn new(tick_rate: u64) -> Self { + let tick_rate = Duration::from_millis(tick_rate); + let (sender, receiver) = mpsc::unbounded_channel(); + let _sender = sender.clone(); + let handler = tokio::spawn(async move { + let mut reader = crossterm::event::EventStream::new(); + let mut tick = tokio::time::interval(tick_rate); + loop { + let tick_delay = tick.tick(); + let crossterm_event = reader.next().fuse(); + tokio::select! { + _ = _sender.closed() => { + break; + } + _ = tick_delay => { + _sender.send(Event::Tick).unwrap(); + } + Some(Ok(evt)) = crossterm_event => { + match evt { + CrosstermEvent::Key(key) => { + if key.kind == crossterm::event::KeyEventKind::Press { + _sender.send(Event::Key(key)).unwrap(); + } + }, + CrosstermEvent::Mouse(mouse) => { + _sender.send(Event::Mouse(mouse)).unwrap(); + }, + CrosstermEvent::Resize(x, y) => { + _sender.send(Event::Resize(x, y)).unwrap(); + }, + CrosstermEvent::FocusLost => { + }, + CrosstermEvent::FocusGained => { + }, + CrosstermEvent::Paste(_) => { + }, + } + } + }; + } + }); + Self { + sender, + receiver, + handler, + } + } + + /// Receive the next event from the handler thread. + /// + /// This function will always block the current thread if + /// there is no data available and it's possible for more data to be sent. + pub async fn next(&mut self) -> AppResult<Event> { + self.receiver + .recv() + .await + .ok_or(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "This is an IO error", + ))) + } +} diff --git a/src/frontend/handler.rs b/src/frontend/handler.rs new file mode 100644 index 0000000..2cc8bb5 --- /dev/null +++ b/src/frontend/handler.rs @@ -0,0 +1,70 @@ +use crate::frontend::app::{App, AppResult}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + +use super::app::CurrentArea; + +/// Handles the key events and updates the state of [`App`]. +pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> { + // Keycodes activated for every area (high priority) + match key_event.code { + // Exit application on `ESC` or `q` + KeyCode::Esc | KeyCode::Char('q') => { + app.quit(); + } + // Exit application on `Ctrl-C` + KeyCode::Char('c') | KeyCode::Char('C') => { + if key_event.modifiers == KeyModifiers::CONTROL { + app.quit(); + } + } + _ => {} + } + // Keycodes for specific areas + match app.current_area { + // Keycodes for the tag area + CurrentArea::TagArea => match key_event.code { + KeyCode::Char('j') | KeyCode::Down => { + app.select_next(); + } + KeyCode::Char('k') | KeyCode::Up => { + app.select_previous(); + } + KeyCode::Char('h') | KeyCode::Left => { + app.select_none(); + } + KeyCode::Char('g') | KeyCode::Home => { + app.select_first(); + } + KeyCode::Char('G') | KeyCode::End => { + app.select_last(); + } + KeyCode::Tab | KeyCode::BackTab => { + app.toggle_area(); + } + _ => {} + }, + // Keycodes for the entry area + CurrentArea::EntryArea => match key_event.code { + KeyCode::Char('j') | KeyCode::Down => { + app.select_next(); + } + KeyCode::Char('k') | KeyCode::Up => { + app.select_previous(); + } + KeyCode::Char('h') | KeyCode::Left => { + app.select_none(); + } + KeyCode::Char('g') | KeyCode::Home => { + app.select_first(); + } + KeyCode::Char('G') | KeyCode::End => { + app.select_last(); + } + KeyCode::Tab | KeyCode::BackTab => { + app.toggle_area(); + } + _ => {} + }, + } + Ok(()) +} diff --git a/src/frontend/tui.rs b/src/frontend/tui.rs new file mode 100644 index 0000000..94db9ea --- /dev/null +++ b/src/frontend/tui.rs @@ -0,0 +1,77 @@ +use crate::frontend::app::{App, AppResult}; +use crate::frontend::event::EventHandler; +use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; +use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; +use ratatui::backend::Backend; +use ratatui::Terminal; +use std::io; +use std::panic; + +/// Representation of a terminal user interface. +/// +/// It is responsible for setting up the terminal, +/// initializing the interface and handling the draw events. +#[derive(Debug)] +pub struct Tui<B: Backend> { + /// Interface to the Terminal. + terminal: Terminal<B>, + /// Terminal event handler. + pub events: EventHandler, +} + +impl<B: Backend> Tui<B> { + /// Constructs a new instance of [`Tui`]. + pub fn new(terminal: Terminal<B>, events: EventHandler) -> Self { + Self { terminal, events } + } + + /// Initializes the terminal interface. + /// + /// It enables the raw mode and sets terminal properties. + pub fn init(&mut self) -> AppResult<()> { + terminal::enable_raw_mode()?; + crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?; + + // Define a custom panic hook to reset the terminal properties. + // This way, you won't have your terminal messed up if an unexpected error happens. + let panic_hook = panic::take_hook(); + panic::set_hook(Box::new(move |panic| { + Self::reset().expect("failed to reset the terminal"); + panic_hook(panic); + })); + + self.terminal.hide_cursor()?; + self.terminal.clear()?; + Ok(()) + } + + /// [`Draw`] the terminal interface by [`rendering`] the widgets. + /// + /// [`Draw`]: ratatui::Terminal::draw + /// [`rendering`]: crate::ui::render + pub fn draw(&mut self, app: &mut App) -> AppResult<()> { + // self.terminal.draw(|frame| ui::render(app, frame))?; + self.terminal + .draw(|frame| frame.render_widget(app, frame.area()))?; + Ok(()) + } + + /// Resets the terminal interface. + /// + /// This function is also used for the panic hook to revert + /// the terminal properties if unexpected errors occur. + fn reset() -> AppResult<()> { + terminal::disable_raw_mode()?; + crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; + Ok(()) + } + + /// Exits the terminal interface. + /// + /// It disables the raw mode and reverts back the terminal properties. + pub fn exit(&mut self) -> AppResult<()> { + Self::reset()?; + self.terminal.show_cursor()?; + Ok(()) + } +} diff --git a/src/frontend/ui.rs b/src/frontend/ui.rs new file mode 100644 index 0000000..ec5e612 --- /dev/null +++ b/src/frontend/ui.rs @@ -0,0 +1,201 @@ +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Layout, Rect}, + style::{ + palette::tailwind::{GRAY, SLATE}, + Color, Modifier, Style, Stylize, + }, + symbols, + text::Line, + widgets::{ + Block, HighlightSpacing, List, ListItem, Padding, Paragraph, StatefulWidget, Widget, Wrap, + }, +}; + +use crate::frontend::app::{App, TagListItem}; + +use super::app::EntryListItem; + +const MAIN_BLUE_COLOR: Color = Color::Indexed(39); +const MAIN_PURPLE_COLOR: Color = Color::Indexed(129); +const BOX_BORDER_STYLE_MAIN: Style = Style::new().fg(Color::White).bg(Color::Black); +const NORMAL_ROW_BG: Color = Color::Black; +const ALT_ROW_BG_COLOR: Color = Color::Indexed(234); +const SELECTED_STYLE: Style = Style::new() + .fg(MAIN_BLUE_COLOR) + .add_modifier(Modifier::BOLD); +const TEXT_FG_COLOR: Color = SLATE.c200; + +pub const fn alternate_colors(i: usize) -> Color { + if i % 2 == 0 { + NORMAL_ROW_BG + } else { + ALT_ROW_BG_COLOR + } +} + +impl From<&TagListItem> for ListItem<'_> { + fn from(value: &TagListItem) -> Self { + let line = Line::styled(format!("{}", value.info), TEXT_FG_COLOR); + // match value.status { + // Status::Todo => Line::styled(format!(" ☐ {}", value.todo), TEXT_FG_COLOR), + // Status::Completed => { + // Line::styled(format!(" ✓ {}", value.todo), COMPLETED_TEXT_FG_COLOR) + // } + // }; + ListItem::new(line) + } +} + +impl From<&EntryListItem> for ListItem<'_> { + fn from(value: &EntryListItem) -> Self { + let line = Line::styled(format!("{}, {}", value.authors, value.title), TEXT_FG_COLOR); + ListItem::new(line) + } +} + +impl Widget for &mut App { + fn render(self, area: Rect, buf: &mut Buffer) { + let [header_area, main_area, footer_area] = Layout::vertical([ + Constraint::Length(2), + Constraint::Fill(1), + Constraint::Length(1), + ]) + .areas(area); + + let [list_area, item_area] = + Layout::vertical([Constraint::Fill(1), Constraint::Fill(1)]).areas(main_area); + + let [tag_area, info_area] = + Layout::horizontal([Constraint::Percentage(30), Constraint::Fill(1)]).areas(item_area); + + // Render header and footer + App::render_header(header_area, buf); + App::render_footer(footer_area, buf); + // Render list area where entry gets selected + self.render_entry_list(list_area, buf); + // Render infos related to selected entry + // TODO: only placeholder at the moment, has to be impl. + self.render_taglist(tag_area, buf); + self.render_selected_item(info_area, buf); + } +} + +impl App { + pub fn render_header(area: Rect, buf: &mut Buffer) { + Paragraph::new("Ratatui List Example") + .bold() + .centered() + .render(area, buf); + } + + pub fn render_footer(area: Rect, buf: &mut Buffer) { + Paragraph::new("Use g/h to move, h to unselect, g/G to go top/bottom.") + .centered() + .render(area, buf); + } + + pub fn render_entry_list(&mut self, area: Rect, buf: &mut Buffer) { + let block = Block::bordered() + .title( + Line::raw(" Selection List ") + .centered() + .fg(Color::Indexed(39)), + ) + // .borders(Borders::TOP) + .border_set(symbols::border::ROUNDED) + .border_style(BOX_BORDER_STYLE_MAIN) + .bg(Color::Black); // .bg(NORMAL_ROW_BG); + + // Iterate through all elements in the `items` and stylize them. + let items: Vec<ListItem> = self + .entry_list + .entry_list_items + .iter() + .enumerate() + .map(|(i, todo_item)| { + let color = alternate_colors(i); + ListItem::from(todo_item).bg(color) + }) + .collect(); + + // Create a List from all list items and highlight the currently selected one + let list = List::new(items) + .block(block) + .highlight_style( + Style::new() + .fg(MAIN_PURPLE_COLOR) + .add_modifier(Modifier::BOLD), + ) + // .highlight_symbol("> ") + .highlight_spacing(HighlightSpacing::Always); + + // We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the + // same method name `render`. + StatefulWidget::render(list, area, buf, &mut self.entry_list.entry_list_state); + } + + pub fn render_selected_item(&self, area: Rect, buf: &mut Buffer) { + // We get the info depending on the item's state. + // INFO: Only a placeholder at the moment: + let info = "Infor for selected item".to_string(); + // TODO: Implement logic showin informations for selected entry: + // let info = if let Some(i) = self.tag_list.state.selected() { + // "Infor for selected item".to_string() + // // match self.todo_list.items[i].status { + // // Status::Completed => format!("✓ DONE: {}", self.todo_list.items[i].info), + // // Status::Todo => format!("☐ TODO: {}", self.todo_list.items[i].info), + // // } + // } else { + // "Nothing selected...".to_string() + // }; + + // We show the list item's info under the list in this paragraph + let block = Block::bordered() + .title(Line::raw(" Item Info ").centered()) + // .borders(Borders::TOP) + .border_set(symbols::border::ROUNDED) + .border_style(BOX_BORDER_STYLE_MAIN) + .bg(Color::Black) + .padding(Padding::horizontal(1)); + + // We can now render the item info + Paragraph::new(info) + .block(block) + .fg(TEXT_FG_COLOR) + .wrap(Wrap { trim: false }) + .render(area, buf); + } + + pub fn render_taglist(&mut self, area: Rect, buf: &mut Buffer) { + let block = Block::bordered() + .title(Line::raw(" Tag List ").centered()) + .border_set(symbols::border::ROUNDED) + .border_style(BOX_BORDER_STYLE_MAIN) + .bg(Color::Black) + .padding(Padding::horizontal(1)); + + // Iterate through all elements in the `items` and stylize them. + let items: Vec<ListItem> = self + .tag_list + .tag_list_items + .iter() + .enumerate() + .map(|(i, todo_item)| { + let color = alternate_colors(i); + ListItem::from(todo_item).bg(color) + }) + .collect(); + + // Create a List from all list items and highlight the currently selected one + let list = List::new(items) + .block(block) + .highlight_style(SELECTED_STYLE) + .highlight_symbol("> ") + .highlight_spacing(HighlightSpacing::Always); + + // We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the + // same method name `render`. + StatefulWidget::render(list, area, buf, &mut self.tag_list.tag_list_state); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d199c9e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,67 @@ +use std::{fs, io}; + +use backend::cliargs::{self, CLIArgs}; +use ratatui::{backend::CrosstermBackend, Terminal}; + +use crate::{ + frontend::app::{App, AppResult}, + frontend::event::{Event, EventHandler}, + frontend::handler::handle_key_events, + frontend::tui::Tui, +}; + +use sarge::prelude::*; + +pub mod backend; +pub mod frontend; + +#[tokio::main] +async fn main() -> AppResult<()> { + // Parse CLI arguments + let parsed_args = CLIArgs::parse_cli_args(); + + // Print help if -h/--help flag is passed and exit + if parsed_args.helparg { + println!("{}", cliargs::help_func()); + std::process::exit(0); + } + + if parsed_args.versionarg { + // println!("Version Zero"); + println!("{}", cliargs::version_func()); + std::process::exit(0); + } + // TODO: Implement logic for CLI arguments/options which need to be handled + // before the TUI is started + + // Create an application. + let mut app = App::new(); + + // TEST: Get Data from main bibliography + // let bibfile = fs::read_to_string("test.bib").unwrap(); + // let biblio = Bibliography::parse(&bibfile).unwrap(); + + // Initialize the terminal user interface. + let backend = CrosstermBackend::new(io::stdout()); + let terminal = Terminal::new(backend)?; + let events = EventHandler::new(250); + let mut tui = Tui::new(terminal, events); + tui.init()?; + + // Start the main loop. + while app.running { + // Render the user interface. + tui.draw(&mut app)?; + // Handle events. + match tui.events.next().await? { + Event::Tick => app.tick(), + Event::Key(key_event) => handle_key_events(key_event, &mut app)?, + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + } + } + + // Exit the user interface. + tui.exit()?; + Ok(()) +} diff --git a/test.bib b/test.bib new file mode 100644 index 0000000..a4b6dbd --- /dev/null +++ b/test.bib @@ -0,0 +1,48 @@ +@online{how_tex_macros_actually_work, + title = {How {TeX} macros actually work}, + shorttitle = {How {TeX} macros actually work}, + url = { + https://www.overleaf.com/learn/latex/How\_TeX\_macros\_actually\_work\%3A\_Part\_1 + }, + urldate = {2024-04-05}, + note = {6 parts}, + organization = {Overleaf}, + abstract = { + An online {LaTeX} editor that's easy to use. No installation, real-time + collaboration, version control, hundreds of {LaTeX} templates, and + more. + }, + langid = {english}, + shorthand = {Overleaf: TeX macros}, +} + +@online{grandsire_the_metafonttutorial_2004, + title = {The METAFONTtutorial}, + author = {Grandsire, Christophe}, + url = {http://metafont.tutorial.free.fr/downloads/mftut.pdf}, + urldate = {2024-04-05}, + date = {2004-12-30}, + version = {0.33}, +} + +@online{gruber_markdown, + title = {Daring Fireball}, + author = {Gruber, John and Pan, Petra}, + url = {https://daringfireball.net/projects/markdown/}, + urldate = {2024-04-08}, + organization = {The Daring Fireball Company LLC}, + shorthand = {Gruber: Markdown}, +} + +@online{skibinski_automated_jats_xml_to_pdf_conversion_2018, + title = {Automated JATS XML to PDF conversion}, + author = {Skibinski, Luke}, + url = {https://github.com/elifesciences/jats-xml-to-pdf/blob/master/report.md}, + urldate = {2024-04-04}, + note = {Zitiert nach der PDF Version}, + date = {2018-11-09}, + organization = {eLife Sciences Publications Ltd}, + file = { + :/home/lukeflo/Documents/literature/skibinski\_automated\_jats\_xml\_to\_pdf\_conversion\_2018.pdf:PDF + }, +} |
