aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock960
-rw-r--r--Cargo.toml15
-rw-r--r--README.md1
-rw-r--r--plainfile3
-rw-r--r--src/backend.rs2
-rw-r--r--src/backend/bib.rs51
-rw-r--r--src/backend/cliargs.rs89
-rw-r--r--src/frontend.rs5
-rw-r--r--src/frontend/app.rs221
-rw-r--r--src/frontend/event.rs97
-rw-r--r--src/frontend/handler.rs70
-rw-r--r--src/frontend/tui.rs77
-rw-r--r--src/frontend/ui.rs201
-rw-r--r--src/main.rs67
-rw-r--r--test.bib48
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
+ },
+}