commit f9c3edee06081ca1403c274e8a15bcabb27f5553 Author: Arijit Basu Date: Mon Mar 1 16:53:04 2021 +0530 Not yet doing what it's supposed to diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1ce19b9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1118 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bstr" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "cast" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "criterion" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools 0.10.0", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" +dependencies = [ + "cast", + "itertools 0.9.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "crossterm" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb" +dependencies = [ + "bitflags", + "crossterm_winapi", + "lazy_static", + "libc", + "mio", + "parking_lot", + "signal-hook", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db" +dependencies = [ + "winapi", +] + +[[package]] +name = "csv" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dtoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" + +[[package]] +name = "handlebars" +version = "3.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb0867bbc5a3da37a753e78021d5fcf8a4db00e18dd2dd90fd36e24190e162d" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error", + "serde", + "serde_json", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "memoffset" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5dede4e2065b3842b8b0af444119f3aa331cc7cc2dd20388bfb0f5d5a38823a" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" +dependencies = [ + "socket2", + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.5", + "smallvec", + "winapi", +] + +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + +[[package]] +name = "plotters" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" + +[[package]] +name = "plotters-svg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_syscall" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_termios" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" +dependencies = [ + "redox_syscall 0.2.5", +] + +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom", + "redox_syscall 0.1.57", + "rust-argon2", +] + +[[package]] +name = "regex" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] + +[[package]] +name = "regex-syntax" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" + +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_cbor" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "signal-hook" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +dependencies = [ + "libc", + "mio", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if", + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termion" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +dependencies = [ + "libc", + "numtoa", + "redox_syscall 0.2.5", + "redox_termios", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "tinytemplate" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tui" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9" +dependencies = [ + "bitflags", + "cassowary", + "crossterm", + "serde", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1" + +[[package]] +name = "web-sys" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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 = "xplr" +version = "0.1.0" +dependencies = [ + "criterion", + "crossterm", + "dirs", + "handlebars", + "serde", + "serde_yaml", + "termion", + "tui", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d4a2d39 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "xplr" +version = "0.1.0" +authors = ["Arijit Basu "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tui = { version = "0.14", default-features = false, features = ['crossterm', 'serde'] } +termion = "1.5" +crossterm = "0.18" +dirs = "3.0.1" +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.8" +handlebars = "3.5.3" + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "navigation" +harness = false diff --git a/README.md b/README.md new file mode 100644 index 0000000..20963e2 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +A Hackable TUI File Explorer +============================ + +Don't run it without copying the `config.yml` inside `~/.config/xplr`. diff --git a/benches/navigation.rs b/benches/navigation.rs new file mode 100644 index 0000000..9d8aff7 --- /dev/null +++ b/benches/navigation.rs @@ -0,0 +1,46 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use std::fs; +use xplr::*; + +fn criterion_benchmark(c: &mut Criterion) { + let app = app::create() + .expect("failed to create app") + .change_directory(&"/tmp/xplr_bench".to_string()) + .unwrap(); + + fs::create_dir_all("/tmp/xplr_bench").unwrap(); + (1..10000).for_each(|i| { + fs::File::create(format!("/tmp/xplr_bench/{}", i)).unwrap(); + }); + + c.bench_function("focus next item", |b| { + b.iter(|| { + app.clone() + .handle(&config::Action::Global(config::GlobalAction::FocusNextItem)) + }) + }); + + c.bench_function("focus previous item", |b| { + b.iter(|| { + app.clone().handle(&config::Action::Global( + config::GlobalAction::FocusPreviousItem, + )) + }) + }); + + c.bench_function("focus first item", |b| { + b.iter(|| { + app.clone() + .handle(&config::Action::Global(config::GlobalAction::FocusFirstItem)) + }) + }); + + c.bench_function("focus last item", |b| { + b.iter(|| { + app.clone() + .handle(&config::Action::Global(config::GlobalAction::FocusLastItem)) + }) + }); +} +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..cc33b9f --- /dev/null +++ b/config.yml @@ -0,0 +1,161 @@ +general: + show_hidden: false + table: + header: + cols: + - format: "│ path" + - format: "is symlink" + - format: "index" + height: 1 + style: + add_modifier: + bits: 1 + sub_modifier: + bits: 0 + row: + cols: + - format: "{{tree}}{{prefix}}{{icon}} {{relativePath}}{{#if isDir}}/{{/if}}{{suffix}}" + - format: "{{isSymlink}}" + - format: "{{focusRelativeIndex}}/{{bufferRelativeIndex}}/{{index}}/{{totalItems}}" + + col_spacing: 3 + col_widths: + - percentage: 60 + - percentage: 20 + - percentage: 20 + + tree: + - format: "├─" + - format: "├─" + - format: "╰─" + + normal_ui: + prefix: " " + suffix: " " + + focused_ui: + prefix: "▸ [" + suffix: "]" + style: + fg: Blue + add_modifier: + bits: 1 + sub_modifier: + bits: 0 + + selected_ui: + prefix: " {" + suffix: "}" + style: + fg: LightGreen + add_modifier: + bits: 0 + sub_modifier: + bits: 0 + +filetypes: + directory: + icon: "" + style: + add_modifier: + bits: 1 + sub_modifier: + bits: 0 + + file: + icon: "" + + symlink: + icon: "" + style: + fg: Blue + add_modifier: + bits: 1 + sub_modifier: + bits: 0 + + extension: + md: + icon: "" + lock: + icon: "" + + special: + poetry.lock: + icon: "" + +key_bindings: + global: + ctrl-c: + help: quit + actions: + - Quit + q: + help: quit + actions: + - Quit + escape: + help: quit + actions: + - Quit + left: + help: back + actions: + - Back + dot: + help: toggle show hidden + actions: + - ToggleShowHidden + right: + help: enter + actions: + - Enter + up: + help: up + actions: + - FocusPreviousItem + down: + help: down + actions: + - FocusNextItem + + shift-g: + help: bottom + actions: + - FocusLastItem + + tilda: + help: go home + actions: + - ChangeDirectory: ~ + + forward-slash: + help: go root + actions: + - ChangeDirectory: / # not working for some reason + + explore_mode: + space: + help: select + actions: + - Select + - FocusNextItem + g: + help: go to + actions: + - EnterSubmode: GoTo + + explore_submodes: + GoTo: + g: + help: top + actions: + - FocusFirstItem + - ExitSubmode + + select_mode: + space: + help: toggle selection + actions: + - ToggleSelection + - FocusNextItem diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..73a0e4f --- /dev/null +++ b/src/app.rs @@ -0,0 +1,757 @@ +use crate::config::{Action, Config, ExploreModeAction, GlobalAction, Mode, SelectModeAction}; +use crate::error::Error; +use crate::input::Key; +use dirs; +use serde::{Deserialize, Serialize}; +use serde_yaml; +use std::collections::HashMap; +use std::collections::HashSet; +use std::fs; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; +use std::path::PathBuf; + +pub const UNSUPPORTED_STR: &str = "???"; +pub const TOTAL_ROWS: usize = 50; + +pub const TEMPLATE_TABLE_ROW: &str = "TEMPLATE_TABLE_ROW"; + +fn expand_tilde>(path_user_input: P) -> Option { + let p = path_user_input.as_ref(); + if !p.starts_with("~") { + return Some(p.to_path_buf()); + } + if p == Path::new("~") { + return dirs::home_dir(); + } + dirs::home_dir().map(|mut h| { + if h == Path::new("/") { + // Corner case: `h` root directory; + // don't prepend extra `/`, just drop the tilde. + p.strip_prefix("~").unwrap().to_path_buf() + } else { + h.push(p.strip_prefix("~/").unwrap()); + h + } + }) +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct DirectoryBuffer { + pub focus: Option, + pub items: Vec<(PathBuf, DirectoryItemMetadata)>, + pub total: usize, +} + +impl DirectoryBuffer { + pub fn relative_focus(focus: usize) -> usize { + focus.min(TOTAL_ROWS) + } + + pub fn explore( + path: &PathBuf, + show_hidden: bool, + ) -> Result<(usize, impl Iterator), Error> { + let hide_hidden = !show_hidden; + + let total = fs::read_dir(&path)? + .filter_map(|d| d.ok().map(|e| e.path())) + .filter_map(|p| p.canonicalize().ok()) + .filter_map(|abs_path| { + abs_path + .file_name() + .map(|rel_path| rel_path.to_str().unwrap_or(UNSUPPORTED_STR).to_string()) + }) + .filter(|rel_path| !(hide_hidden && rel_path.starts_with('.'))) + .count(); + + let items = fs::read_dir(&path)? + .filter_map(|d| d.ok().map(|e| e.path())) + .filter_map(|p| p.canonicalize().ok()) + .filter_map(|abs_path| { + abs_path.file_name().map(|rel_path| { + ( + abs_path.to_path_buf(), + rel_path.to_str().unwrap_or(UNSUPPORTED_STR).to_string(), + ) + }) + }) + .filter(move |(_, rel_path)| !(hide_hidden && rel_path.starts_with('.'))); + Ok((total, items)) + } + + pub fn load( + config: &Config, + focus: Option, + path: &PathBuf, + show_hidden: bool, + selected_paths: &HashSet, + ) -> Result { + let offset = focus + .map(|f| (f.max(TOTAL_ROWS) - TOTAL_ROWS, f.max(TOTAL_ROWS))) + .unwrap_or((0, TOTAL_ROWS)); + + let (total, items) = DirectoryBuffer::explore(&path, show_hidden)?; + let visible: Vec<(PathBuf, DirectoryItemMetadata)> = items + .enumerate() + .skip_while(|(i, _)| *i < offset.0) + .take_while(|(i, _)| *i <= offset.1) + .enumerate() + .map(|(rel_idx, (net_idx, (abs, rel)))| { + let ext = abs + .extension() + .and_then(|s| s.to_str()) + .unwrap_or("") + .to_string(); + (net_idx, rel_idx, abs, rel, ext) + }) + .map(|(net_idx, rel_idx, abs, rel, ext)| { + let absolute_path: String = + abs.as_os_str().to_str().unwrap_or(UNSUPPORTED_STR).into(); + let relative_path = rel.to_string(); + let extension = ext.to_string(); + let is_dir = abs.is_dir(); + let is_file = abs.is_file(); + + let maybe_meta = abs.metadata().ok(); + + let is_symlink = maybe_meta + .clone() + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false); + + let is_readonly = maybe_meta + .clone() + .map(|m| m.permissions().readonly()) + .unwrap_or(false); + + let (focus_idx, is_focused) = + focus.map(|f| (f, net_idx == f)).unwrap_or((0, false)); + let is_selected = selected_paths.contains(&abs); + + let ui = if is_focused { + &config.general.focused_ui + } else if is_selected { + &config.general.selected_ui + } else { + &config.general.normal_ui + }; + + let is_first_item = net_idx == 0; + let is_last_item = net_idx == total.max(1) - 1; + + let tree = config + .general + .table + .tree + .clone() + .map(|t| { + if is_last_item { + t.2.format.clone() + } else if is_first_item { + t.0.format.clone() + } else { + t.1.format.clone() + } + }) + .unwrap_or_default(); + + let filetype = config + .filetypes + .special + .get(&relative_path) + .or_else(|| config.filetypes.extension.get(&extension)) + .unwrap_or_else(|| { + if is_symlink { + &config.filetypes.symlink + } else if is_dir { + &config.filetypes.directory + } else { + &config.filetypes.file + } + }); + + let focus_relative_index = if focus_idx <= net_idx { + format!(" {}", net_idx - focus_idx) + } else { + format!("-{}", focus_idx - net_idx) + }; + + let m = DirectoryItemMetadata { + absolute_path, + relative_path, + extension, + icon: filetype.icon.clone(), + prefix: ui.prefix.clone(), + suffix: ui.suffix.clone(), + tree: tree.into(), + is_symlink, + is_first_item, + is_last_item, + is_dir, + is_file, + is_readonly, + is_selected, + is_focused, + index: net_idx + 1, + focus_relative_index, + buffer_relative_index: rel_idx + 1, + total_items: total, + }; + (abs.to_owned(), m) + }) + .collect(); + + let focus = focus.map(|f| { + if Self::relative_focus(f) >= visible.len() { + visible.len().max(1) - 1 + } else { + f + } + }); + + Ok(Self { + total, + items: visible, + focus, + }) + } + + pub fn focused_item(&self) -> Option<(PathBuf, DirectoryItemMetadata)> { + self.focus.and_then(|f| { + self.items + .get(Self::relative_focus(f)) + .map(|f| f.to_owned()) + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DirectoryItemMetadata { + pub absolute_path: String, + pub relative_path: String, + pub extension: String, + pub icon: String, + pub prefix: String, + pub suffix: String, + pub tree: String, + pub is_first_item: bool, + pub is_last_item: bool, + pub is_symlink: bool, + pub is_dir: bool, + pub is_file: bool, + pub is_readonly: bool, + pub is_selected: bool, + pub is_focused: bool, + pub index: usize, + pub focus_relative_index: String, + pub buffer_relative_index: usize, + pub total_items: usize, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct App { + pub config: Config, + pub pwd: PathBuf, + pub directory_buffer: DirectoryBuffer, + pub saved_buffers: HashMap, + pub selected_paths: HashSet, + pub mode: Mode, + pub show_hidden: bool, + pub result: Option, +} + +impl App { + pub fn new( + config: &Config, + pwd: &PathBuf, + saved_buffers: &HashMap, + selected_paths: &HashSet, + mode: Mode, + show_hidden: bool, + focus: Option, + ) -> Result { + let directory_buffer = + DirectoryBuffer::load(config, focus.or(Some(0)), pwd, show_hidden, selected_paths)?; + + let mut saved_buffers = saved_buffers.clone(); + saved_buffers.insert(pwd.into(), directory_buffer.to_owned()); + + Ok(Self { + config: config.to_owned(), + pwd: pwd.to_owned(), + directory_buffer, + saved_buffers, + selected_paths: selected_paths.to_owned(), + mode, + show_hidden, + result: None, + }) + } + + pub fn exit_submode(self) -> Result { + let mut app = self; + let mode = match app.mode { + Mode::ExploreSubmode(_) => Mode::Explore, + Mode::SelectSubmode(_) => Mode::Select, + m => m, + }; + app.mode = mode; + Ok(app) + } + + pub fn toggle_hidden(self) -> Result { + Self::new( + &self.config, + &self.pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode, + !self.show_hidden, + self.directory_buffer.focus, + ) + } + + pub fn focus_first_item(self) -> Result { + let focus = if self.directory_buffer.total == 0 { + None + } else { + Some(0) + }; + + Self::new( + &self.config, + &self.pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode, + self.show_hidden, + focus, + ) + } + + pub fn focus_last_item(self) -> Result { + let focus = if self.directory_buffer.total == 0 { + None + } else { + Some(self.directory_buffer.total - 1) + }; + + Self::new( + &self.config, + &self.pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode, + self.show_hidden, + focus, + ) + } + + pub fn change_directory(self, dir: &String) -> Result { + self.focus_path(&PathBuf::from(dir))?.enter() + } + + pub fn focus_next_item(self) -> Result { + let len = self.directory_buffer.total; + let focus = self + .directory_buffer + .focus + .map(|f| (len - 1).min(f + 1)) + .or(Some(0)); + + Self::new( + &self.config, + &self.pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode, + self.show_hidden, + focus, + ) + } + + pub fn focus_previous_item(self) -> Result { + let len = self.directory_buffer.total; + let focus = if len == 0 { + None + } else { + self.directory_buffer + .focus + .map(|f| Some(1.max(f) - 1)) + .unwrap_or(Some(len - 1)) + }; + + Self::new( + &self.config, + &self.pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode, + self.show_hidden, + focus, + ) + } + + pub fn focus_path(self, path: &PathBuf) -> Result { + expand_tilde(path) + .unwrap_or(path.into()) + .parent() + .map(|pwd| { + let (_, items) = DirectoryBuffer::explore(&pwd.into(), self.show_hidden)?; + let focus = items + .enumerate() + .find_map(|(i, (p, _))| { + if p.as_path() == path.as_path() { + Some(i) + } else { + None + } + }) + .or(Some(0)); + + Self::new( + &self.config, + &pwd.into(), + &self.saved_buffers, + &self.selected_paths, + self.mode.clone(), + self.show_hidden, + focus, + ) + }) + .unwrap_or_else(|| Ok(self.to_owned())) + } + + pub fn focus_by_index(self, idx: &usize) -> Result { + Self::new( + &self.config, + &self.pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode.clone(), + self.show_hidden, + Some(idx.clone()), + ) + } + + pub fn focus_by_buffer_relative_index(self, idx: &usize) -> Result { + Self::new( + &self.config, + &self.pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode.clone(), + self.show_hidden, + Some(DirectoryBuffer::relative_focus(idx.clone())), + ) + } + + pub fn focus_by_focus_relative_index(self, idx: &isize) -> Result { + Self::new( + &self.config, + &self.pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode.clone(), + self.show_hidden, + self.directory_buffer + .focus + .map(|f| ((f as isize) + idx).min(0) as usize), // TODO: make it safer + ) + } + + pub fn enter(self) -> Result { + let pwd = self + .directory_buffer + .focused_item() + .map(|(p, _)| p) + .map(|p| if p.is_dir() { p } else { self.pwd.clone() }) + .unwrap_or_else(|| self.pwd.clone()); + + let focus = self.saved_buffers.get(&pwd).and_then(|b| b.focus); + + Self::new( + &self.config, + &pwd, + &self.saved_buffers, + &self.selected_paths, + self.mode, + self.show_hidden, + focus, + ) + } + + pub fn back(self) -> Result { + let app = self.clone(); + self.focus_path(&app.pwd) + } + + pub fn select(self) -> Result { + let selected_paths = self + .directory_buffer + .focused_item() + .map(|(p, _)| { + let mut selected_paths = self.selected_paths.clone(); + selected_paths.insert(p); + selected_paths + }) + .unwrap_or_else(|| self.selected_paths.clone()); + + let mut app = self; + app.selected_paths = selected_paths; + app.mode = Mode::Select; + Ok(app) + } + + pub fn toggle_selection(self) -> Result { + let selected_paths = self + .directory_buffer + .focused_item() + .map(|(p, _)| { + let mut selected_paths = self.selected_paths.clone(); + if selected_paths.contains(&p) { + selected_paths.remove(&p); + } else { + selected_paths.insert(p); + } + selected_paths + }) + .unwrap_or_else(|| self.selected_paths.clone()); + + let mode = if selected_paths.len() == 0 { + Mode::Explore + } else { + Mode::Select + }; + + let mut app = self; + app.selected_paths = selected_paths; + app.mode = mode; + Ok(app) + } + + pub fn enter_submode(self, submode: &String) -> Result { + let mut app = self; + app.mode = Mode::ExploreSubmode(submode.clone()); + Ok(app) + } + + pub fn print_focused_and_quit(self) -> Result { + let mut app = self; + app.result = app + .directory_buffer + .focused_item() + .and_then(|(p, _)| p.to_str().map(|s| s.to_string())); + Ok(app) + } + + pub fn print_pwd_and_quit(self) -> Result { + let mut app = self; + app.result = app.pwd.to_str().map(|s| s.to_string()); + Ok(app) + } + + pub fn print_selected_and_quit(self) -> Result { + let mut app = self; + app.result = Some( + app.selected_paths + .clone() + .iter() + .filter_map(|p| p.to_str()) + .map(|s| s.to_string()) + .collect::>() + .join("\n"), + ); + Ok(app) + } + + pub fn print_app_state_and_quit(self) -> Result { + let state = serde_yaml::to_string(&self)?; + let mut app = self; + app.result = Some(state); + Ok(app) + } + + pub fn quit(self) -> Result { + Err(Error::Interrupted) + } + + pub fn actions_from_key(&self, key: Key) -> Option> { + self.config + .key_bindings + .global + .get(&key) + .map(|m| { + m.actions + .iter() + .map(|a| Action::Global(a.clone())) + .collect() + }) + .or_else(|| match &self.mode { + Mode::Explore => self.config.key_bindings.explore_mode.get(&key).map(|m| { + m.actions + .iter() + .map(|a| Action::ExploreMode(a.clone())) + .collect() + }), + + Mode::Select => self.config.key_bindings.select_mode.get(&key).map(|m| { + m.actions + .iter() + .map(|a| Action::SelectMode(a.clone())) + .collect() + }), + + Mode::ExploreSubmode(sub) => self + .config + .key_bindings + .explore_submodes + .get(sub) + .and_then(|kb| { + kb.get(&key).map(|m| { + m.actions + .iter() + .map(|a| Action::ExploreMode(a.clone())) + .collect() + }) + }), + + Mode::SelectSubmode(sub) => self + .config + .key_bindings + .select_submodes + .get(sub) + .and_then(|kb| { + kb.get(&key).map(|m| { + m.actions + .iter() + .map(|a| Action::SelectMode(a.clone())) + .collect() + }) + }), + }) + } + + pub fn handle(self, action: &Action) -> Result { + match action { + // Global actions + Action::Global(GlobalAction::ToggleShowHidden) => self.toggle_hidden(), + Action::Global(GlobalAction::Back) => self.back(), + Action::Global(GlobalAction::Enter) => self.enter(), + Action::Global(GlobalAction::FocusNext) => self.focus_next_item(), + Action::Global(GlobalAction::FocusPrevious) => self.focus_previous_item(), + Action::Global(GlobalAction::FocusFirst) => self.focus_first_item(), + Action::Global(GlobalAction::FocusLast) => self.focus_last_item(), + Action::Global(GlobalAction::FocusPath(path)) => self.focus_path(&path.into()), + Action::Global(GlobalAction::FocusPathByIndex(n)) => self.focus_by_index(n), + Action::Global(GlobalAction::FocusPathByBufferRelativeIndex(n)) => { + self.focus_by_buffer_relative_index(&n) + } + Action::Global(GlobalAction::FocusPathByFocusRelativeIndex(n)) => { + self.focus_by_focus_relative_index(&n) + } + Action::Global(GlobalAction::ChangeDirectory(dir)) => self.change_directory(&dir), + Action::Global(GlobalAction::PrintPwdAndQuit) => self.print_pwd_and_quit(), + Action::Global(GlobalAction::PrintAppStateAndQuit) => self.print_app_state_and_quit(), + Action::Global(GlobalAction::Quit) => self.quit(), + + // Explore mode + Action::ExploreMode(ExploreModeAction::ToggleShowHidden) => self.toggle_hidden(), + Action::ExploreMode(ExploreModeAction::Back) => self.back(), + Action::ExploreMode(ExploreModeAction::Enter) => self.enter(), + Action::ExploreMode(ExploreModeAction::FocusNext) => self.focus_next_item(), + Action::ExploreMode(ExploreModeAction::FocusPrevious) => self.focus_previous_item(), + Action::ExploreMode(ExploreModeAction::FocusFirst) => self.focus_first_item(), + Action::ExploreMode(ExploreModeAction::FocusLast) => self.focus_last_item(), + Action::ExploreMode(ExploreModeAction::FocusPath(path)) => { + self.focus_path(&path.into()) + } + Action::ExploreMode(ExploreModeAction::FocusPathByIndex(n)) => self.focus_by_index(n), + Action::ExploreMode(ExploreModeAction::FocusPathByBufferRelativeIndex(n)) => { + self.focus_by_buffer_relative_index(&n) + } + Action::ExploreMode(ExploreModeAction::FocusPathByFocusRelativeIndex(n)) => { + self.focus_by_focus_relative_index(&n) + } + Action::ExploreMode(ExploreModeAction::ChangeDirectory(dir)) => { + self.change_directory(&dir) + } + Action::ExploreMode(ExploreModeAction::Select) => self.select(), + Action::ExploreMode(ExploreModeAction::EnterSubmode(submode)) => { + self.enter_submode(submode) + } + Action::ExploreMode(ExploreModeAction::ExitSubmode) => self.exit_submode(), + Action::ExploreMode(ExploreModeAction::PrintFocusedAndQuit) => { + self.print_focused_and_quit() + } + Action::ExploreMode(ExploreModeAction::PrintPwdAndQuit) => self.print_pwd_and_quit(), + Action::ExploreMode(ExploreModeAction::PrintAppStateAndQuit) => { + self.print_app_state_and_quit() + } + Action::ExploreMode(ExploreModeAction::Quit) => self.quit(), + + // Select mode + Action::SelectMode(SelectModeAction::ToggleShowHidden) => self.toggle_hidden(), + Action::SelectMode(SelectModeAction::Back) => self.back(), + Action::SelectMode(SelectModeAction::Enter) => self.enter(), + Action::SelectMode(SelectModeAction::FocusNext) => self.focus_next_item(), + Action::SelectMode(SelectModeAction::FocusPrevious) => self.focus_previous_item(), + Action::SelectMode(SelectModeAction::FocusFirst) => self.focus_first_item(), + Action::SelectMode(SelectModeAction::FocusLast) => self.focus_last_item(), + Action::SelectMode(SelectModeAction::FocusPath(path)) => self.focus_path(&path.into()), + Action::SelectMode(SelectModeAction::FocusPathByIndex(n)) => self.focus_by_index(n), + Action::SelectMode(SelectModeAction::FocusPathByBufferRelativeIndex(n)) => { + self.focus_by_buffer_relative_index(&n) + } + Action::SelectMode(SelectModeAction::FocusPathByFocusRelativeIndex(n)) => { + self.focus_by_focus_relative_index(&n) + } + Action::SelectMode(SelectModeAction::ChangeDirectory(dir)) => { + self.change_directory(&dir) + } + Action::SelectMode(SelectModeAction::ToggleSelection) => self.toggle_selection(), + Action::SelectMode(SelectModeAction::EnterSubmode(submode)) => { + self.enter_submode(submode) + } + Action::SelectMode(SelectModeAction::ExitSubmode) => self.exit_submode(), + Action::SelectMode(SelectModeAction::PrintSelectedAndQuit) => { + self.print_selected_and_quit() + } + Action::SelectMode(SelectModeAction::PrintAppStateAndQuit) => { + self.print_app_state_and_quit() + } + Action::SelectMode(SelectModeAction::Quit) => self.quit(), + } + } +} + +pub fn create() -> Result { + let config_dir = dirs::config_dir() + .unwrap_or(PathBuf::from(".")) + .join("xplr"); + + let config_file = config_dir.join("config.yml"); + + let config: Config = if config_file.exists() { + serde_yaml::from_reader(BufReader::new(&File::open(config_file)?))? + } else { + Config::default() + }; + + let pwd = PathBuf::from("./") + .canonicalize() + .unwrap_or(PathBuf::from("/")); + + App::new( + &config, + &pwd, + &Default::default(), + &Default::default(), + Mode::Explore, + config.general.show_hidden, + None, + ) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..c8927ef --- /dev/null +++ b/src/config.rs @@ -0,0 +1,492 @@ +use crate::input::Key; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use tui::layout::Constraint as TUIConstraint; +use tui::style::Color; +use tui::style::Modifier; +use tui::style::Style; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Mode { + Explore, + ExploreSubmode(String), + Select, + SelectSubmode(String), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Format { + Line, + Pretty, + Yaml, + YamlPretty, + Template(String), +} + +impl Default for Format { + fn default() -> Self { + Self::Line + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum GlobalAction { + + // Common actions + ToggleShowHidden, + Back, + Enter, + FocusPrevious, + FocusNext, + FocusFirst, + FocusLast, + FocusPath(String), + FocusPathByIndex(usize), + FocusPathByBufferRelativeIndex(usize), + FocusPathByFocusRelativeIndex(isize), + ChangeDirectory(String), + + // Quit options + PrintPwdAndQuit, + PrintAppStateAndQuit, + Quit, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ExploreModeAction { + + // Common actions + ToggleShowHidden, + Back, + Enter, + FocusPrevious, + FocusNext, + FocusFirst, + FocusLast, + FocusPathByIndex(usize), + FocusPathByBufferRelativeIndex(usize), + FocusPathByFocusRelativeIndex(isize), + FocusPath(String), + ChangeDirectory(String), + + // Explore mode exclusive options + EnterSubmode(String), + ExitSubmode, + Select, + // Unselect, + // SelectAll, + // SelectAllRecursive, + + // Quit options + PrintFocusedAndQuit, + PrintPwdAndQuit, + PrintAppStateAndQuit, + Quit, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum SelectModeAction { + + // Common actions + ToggleShowHidden, + Back, + Enter, + FocusPrevious, + FocusNext, + FocusFirst, + FocusLast, + FocusPathByIndex(usize), + FocusPathByBufferRelativeIndex(usize), + FocusPathByFocusRelativeIndex(isize), + FocusPath(String), + ChangeDirectory(String), + + // Select mode exclusive options + EnterSubmode(String), + ExitSubmode, + // Select, + // Unselect, + // SelectAll, + // SelectAllRecursive, + // UnselectAll, + // UnSelectAllRecursive, + ToggleSelection, + // ClearSelectedPaths, + + // Quit options + PrintSelectedAndQuit, + PrintAppStateAndQuit, + Quit, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Action { + Global(GlobalAction), + ExploreMode(ExploreModeAction), + SelectMode(SelectModeAction), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct GlobalActionMenu { + #[serde(default)] + pub help: String, + pub actions: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ExploreModeActionMenu { + #[serde(default)] + pub help: String, + pub actions: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SelectModeActionMenu { + #[serde(default)] + pub help: String, + pub actions: Vec, +} + +pub type ExploreSubmodeActionMenu = HashMap; +pub type SelectSubmodeActionMenu = HashMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KeyBindings { + pub global: HashMap, + #[serde(default)] + pub explore_mode: HashMap, + #[serde(default)] + pub explore_submodes: HashMap, + #[serde(default)] + pub select_mode: HashMap, + #[serde(default)] + pub select_submodes: HashMap, +} + +impl Default for KeyBindings { + fn default() -> Self { + let mut global: HashMap = Default::default(); + let mut explore_mode: HashMap = Default::default(); + let explore_submodes: HashMap = Default::default(); + let mut select_mode: HashMap = Default::default(); + let select_submodes: HashMap = Default::default(); + + global.insert( + Key::CtrlC, + GlobalActionMenu { + help: "quit".into(), + actions: vec![GlobalAction::Quit], + }, + ); + + global.insert( + Key::Q, + GlobalActionMenu { + help: "quit".into(), + actions: vec![GlobalAction::Quit], + }, + ); + + global.insert( + Key::Escape, + GlobalActionMenu { + help: "quit".into(), + actions: vec![GlobalAction::Quit], + }, + ); + + global.insert( + Key::Left, + GlobalActionMenu { + help: "left".into(), + actions: vec![GlobalAction::Back], + }, + ); + + global.insert( + Key::Dot, + GlobalActionMenu { + help: "show/hide hidden files".into(), + actions: vec![GlobalAction::ToggleShowHidden], + }, + ); + + global.insert( + Key::Right, + GlobalActionMenu { + help: "enter".into(), + actions: vec![GlobalAction::Enter], + }, + ); + + global.insert( + Key::Up, + GlobalActionMenu { + help: "".into(), + actions: vec![GlobalAction::FocusNext], + }, + ); + + global.insert( + Key::G, + GlobalActionMenu { + help: "top".into(), + actions: vec![GlobalAction::FocusFirst], + }, + ); + + global.insert( + Key::ShiftG, + GlobalActionMenu { + help: "bottom".into(), + actions: vec![GlobalAction::FocusLast], + }, + ); + + global.insert( + Key::Tilda, + GlobalActionMenu { + help: "home".into(), + actions: vec![GlobalAction::ChangeDirectory("~".to_string())], + }, + ); + + explore_mode.insert( + Key::Space, + ExploreModeActionMenu { + help: "select".into(), + actions: vec![ExploreModeAction::Select, ExploreModeAction::FocusNext], + }, + ); + + select_mode.insert( + Key::Space, + SelectModeActionMenu { + help: "select/unselect".into(), + actions: vec![ + SelectModeAction::ToggleSelection, + SelectModeAction::FocusNext, + ], + }, + ); + + Self { + global, + explore_mode, + explore_submodes, + select_mode, + select_submodes, + } + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct FileTypeConfig { + #[serde(default)] + pub icon: String, + #[serde(default)] + pub style: Style, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileTypesConfig { + #[serde(default)] + pub directory: FileTypeConfig, + #[serde(default)] + pub file: FileTypeConfig, + #[serde(default)] + pub symlink: FileTypeConfig, + #[serde(default)] + pub extension: HashMap, + #[serde(default)] + pub special: HashMap, +} + +impl Default for FileTypesConfig { + fn default() -> Self { + FileTypesConfig { + directory: FileTypeConfig { + icon: "d".into(), + style: Style::default() + .add_modifier(Modifier::BOLD) + .fg(Color::Blue), + }, + + file: FileTypeConfig { + icon: "f".into(), + style: Default::default(), + }, + + symlink: FileTypeConfig { + icon: "s".into(), + style: Style::default() + .add_modifier(Modifier::ITALIC) + .fg(Color::Cyan), + }, + + extension: Default::default(), + special: Default::default(), + } + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct UIConfig { + #[serde(default)] + pub prefix: String, + #[serde(default)] + pub suffix: String, + #[serde(default)] + pub style: Style, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct UIElement { + #[serde(default)] + pub format: String, + #[serde(default)] + pub style: Style, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TableRowConfig { + #[serde(default)] + pub cols: Vec, + #[serde(default)] + pub style: Style, + #[serde(default)] + pub height: u16, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Constraint { + Percentage(u16), + Ratio(u32, u32), + Length(u16), + Max(u16), + Min(u16), +} + +impl Default for Constraint { + fn default() -> Self { + Self::Min(1) + } +} + +impl Into for Constraint { + fn into(self) -> TUIConstraint { + match self { + Self::Length(n) => TUIConstraint::Length(n), + Self::Percentage(n) => TUIConstraint::Percentage(n), + Self::Ratio(x, y) => TUIConstraint::Ratio(x, y), + Self::Max(n) => TUIConstraint::Max(n), + Self::Min(n) => TUIConstraint::Min(n), + } + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct TableConfig { + #[serde(default)] + pub header: Option, + #[serde(default)] + pub row: TableRowConfig, + #[serde(default)] + pub style: Style, + #[serde(default)] + pub tree: Option<(UIElement, UIElement, UIElement)>, + #[serde(default)] + pub col_spacing: u16, + #[serde(default)] + pub col_widths: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GeneralConfig { + #[serde(default)] + pub show_hidden: bool, + #[serde(default)] + pub table: TableConfig, + #[serde(default)] + pub normal_ui: UIConfig, + #[serde(default)] + pub focused_ui: UIConfig, + #[serde(default)] + pub selected_ui: UIConfig, +} + +impl Default for GeneralConfig { + fn default() -> Self { + Self { + show_hidden: false, + table: TableConfig { + header: None, + row: TableRowConfig { + cols: vec![UIElement { + format: "{{tree}}{{prefix}}{{relativePath}}{{#if isDir}}/{{/if}}{{suffix}}" + .into(), + style: Default::default(), + }], + style: Default::default(), + height: 1, + }, + + style: Default::default(), + tree: Some(( + UIElement { + format: "├─".into(), + style: Default::default(), + }, + UIElement { + format: "├─".into(), + style: Default::default(), + }, + UIElement { + format: "└─".into(), + style: Default::default(), + }, + )), + col_spacing: 1, + col_widths: vec![Constraint::Percentage(100)], + }, + normal_ui: UIConfig { + prefix: " ".into(), + suffix: " ".into(), + style: Default::default(), + }, + + focused_ui: UIConfig { + prefix: "▸ [".into(), + suffix: "]".into(), + style: Style::default().add_modifier(Modifier::BOLD), + }, + + selected_ui: UIConfig { + prefix: " {".into(), + suffix: "}".into(), + style: Style::default().add_modifier(Modifier::BOLD), + }, + } + } +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Config { + #[serde(default)] + pub general: GeneralConfig, + + #[serde(default)] + pub filetypes: FileTypesConfig, + + #[serde(default)] + pub key_bindings: KeyBindings, +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..000b132 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,32 @@ +use std::io; +use handlebars; +use serde_yaml; + +#[derive(Debug)] +pub enum Error { + // Not an error but, + Interrupted, + + // Real errors + TemplateError(handlebars::TemplateError), + YamlError(serde_yaml::Error), + IOError(io::Error), +} + +impl From for Error { + fn from(err: io::Error) -> Self { + Self::IOError(err) + } +} + +impl From for Error { + fn from(err: handlebars::TemplateError) -> Self { + Self::TemplateError(err) + } +} + +impl From for Error { + fn from(err: serde_yaml::Error) -> Self { + Self::YamlError(err) + } +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..b216f9a --- /dev/null +++ b/src/input.rs @@ -0,0 +1,483 @@ +use termion::event::Key as TermionKey; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum Key { + Backspace, + Left, + Right, + Up, + Down, + Home, + End, + PageUp, + PageDown, + BackTab, + Delete, + Insert, + Return, + Space, + Tab, + Escape, + + Zero, + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + + CtrlZero, + CtrlOne, + CtrlTwo, + CtrlThree, + CtrlFour, + CtrlFive, + CtrlSix, + CtrlSeven, + CtrlEight, + CtrlNine, + + AltZero, + AltOne, + AltTwo, + AltThree, + AltFour, + AltFive, + AltSix, + AltSeven, + AltEight, + AltNine, + + ShiftZero, + ShiftOne, + ShiftTwo, + ShiftThree, + ShiftFour, + ShiftFive, + ShiftSix, + ShiftSeven, + ShiftEight, + ShiftNine, + + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + + CtrlA, + CtrlB, + CtrlC, + CtrlD, + CtrlE, + CtrlF, + CtrlG, + CtrlH, + CtrlI, + CtrlJ, + CtrlK, + CtrlL, + CtrlM, + CtrlN, + CtrlO, + CtrlP, + CtrlQ, + CtrlR, + CtrlS, + CtrlT, + CtrlU, + CtrlV, + CtrlW, + CtrlX, + CtrlY, + CtrlZ, + + AltA, + AltB, + AltC, + AltD, + AltE, + AltF, + AltG, + AltH, + AltI, + AltJ, + AltK, + AltL, + AltM, + AltN, + AltO, + AltP, + AltQ, + AltR, + AltS, + AltT, + AltU, + AltV, + AltW, + AltX, + AltY, + AltZ, + + ShiftA, + ShiftB, + ShiftC, + ShiftD, + ShiftE, + ShiftF, + ShiftG, + ShiftH, + ShiftI, + ShiftJ, + ShiftK, + ShiftL, + ShiftM, + ShiftN, + ShiftO, + ShiftP, + ShiftQ, + ShiftR, + ShiftS, + ShiftT, + ShiftU, + ShiftV, + ShiftW, + ShiftX, + ShiftY, + ShiftZ, + + CtrlShiftA, + CtrlShiftB, + CtrlShiftC, + CtrlShiftD, + CtrlShiftE, + CtrlShiftF, + CtrlShiftG, + CtrlShiftH, + CtrlShiftI, + CtrlShiftJ, + CtrlShiftK, + CtrlShiftL, + CtrlShiftM, + CtrlShiftN, + CtrlShiftO, + CtrlShiftP, + CtrlShiftQ, + CtrlShiftR, + CtrlShiftS, + CtrlShiftT, + CtrlShiftU, + CtrlShiftV, + CtrlShiftW, + CtrlShiftX, + CtrlShiftY, + CtrlShiftZ, + + AltShiftA, + AltShiftB, + AltShiftC, + AltShiftD, + AltShiftE, + AltShiftF, + AltShiftG, + AltShiftH, + AltShiftI, + AltShiftJ, + AltShiftK, + AltShiftL, + AltShiftM, + AltShiftN, + AltShiftO, + AltShiftP, + AltShiftQ, + AltShiftR, + AltShiftS, + AltShiftT, + AltShiftU, + AltShiftV, + AltShiftW, + AltShiftX, + AltShiftY, + AltShiftZ, + + Plus, + Minus, + Backtick, + Tilda, + Underscore, + Equals, + Semicolon, + Colon, + SingleQuote, + DoubleQuote, + ForwardSlash, + BackSlash, + Dot, + Comma, + QuestionMark, + + NotSupported, +} + +impl Key { + pub fn from_termion_event(key: TermionKey) -> Self { + match key { + TermionKey::Backspace => Key::Backspace, + TermionKey::Left => Key::Left, + TermionKey::Right => Key::Right, + TermionKey::Up => Key::Up, + TermionKey::Down => Key::Down, + TermionKey::Home => Key::Home, + TermionKey::End => Key::End, + TermionKey::PageUp => Key::PageUp, + TermionKey::PageDown => Key::PageDown, + TermionKey::BackTab => Key::BackTab, + TermionKey::Delete => Key::Delete, + TermionKey::Insert => Key::Insert, + TermionKey::Char('\n') => Key::Return, + TermionKey::Char(' ') => Key::Space, + TermionKey::Char('\t') => Key::Tab, + TermionKey::Esc => Key::Escape, + + TermionKey::Char('0') => Key::Zero, + TermionKey::Char('1') => Key::One, + TermionKey::Char('2') => Key::Two, + TermionKey::Char('3') => Key::Three, + TermionKey::Char('4') => Key::Four, + TermionKey::Char('5') => Key::Five, + TermionKey::Char('6') => Key::Six, + TermionKey::Char('7') => Key::Seven, + TermionKey::Char('8') => Key::Eight, + TermionKey::Char('9') => Key::Nine, + + TermionKey::Ctrl('0') => Key::CtrlZero, + TermionKey::Ctrl('1') => Key::CtrlOne, + TermionKey::Ctrl('2') => Key::CtrlTwo, + TermionKey::Ctrl('3') => Key::CtrlThree, + TermionKey::Ctrl('4') => Key::CtrlFour, + TermionKey::Ctrl('5') => Key::CtrlFive, + TermionKey::Ctrl('6') => Key::CtrlSix, + TermionKey::Ctrl('7') => Key::CtrlSeven, + TermionKey::Ctrl('8') => Key::CtrlEight, + TermionKey::Ctrl('9') => Key::CtrlNine, + + TermionKey::Alt('0') => Key::AltZero, + TermionKey::Alt('1') => Key::AltOne, + TermionKey::Alt('2') => Key::AltTwo, + TermionKey::Alt('3') => Key::AltThree, + TermionKey::Alt('4') => Key::AltFour, + TermionKey::Alt('5') => Key::AltFive, + TermionKey::Alt('6') => Key::AltSix, + TermionKey::Alt('7') => Key::AltSeven, + TermionKey::Alt('8') => Key::AltEight, + TermionKey::Alt('9') => Key::AltNine, + + TermionKey::Char('a') => Key::A, + TermionKey::Char('b') => Key::B, + TermionKey::Char('c') => Key::C, + TermionKey::Char('d') => Key::D, + TermionKey::Char('e') => Key::E, + TermionKey::Char('f') => Key::F, + TermionKey::Char('g') => Key::G, + TermionKey::Char('h') => Key::H, + TermionKey::Char('i') => Key::I, + TermionKey::Char('j') => Key::J, + TermionKey::Char('k') => Key::K, + TermionKey::Char('l') => Key::L, + TermionKey::Char('m') => Key::M, + TermionKey::Char('n') => Key::N, + TermionKey::Char('o') => Key::O, + TermionKey::Char('p') => Key::P, + TermionKey::Char('q') => Key::Q, + TermionKey::Char('r') => Key::R, + TermionKey::Char('s') => Key::S, + TermionKey::Char('t') => Key::T, + TermionKey::Char('u') => Key::U, + TermionKey::Char('v') => Key::V, + TermionKey::Char('w') => Key::W, + TermionKey::Char('x') => Key::X, + TermionKey::Char('y') => Key::Y, + TermionKey::Char('z') => Key::Z, + + TermionKey::Ctrl('a') => Key::CtrlA, + TermionKey::Ctrl('b') => Key::CtrlB, + TermionKey::Ctrl('c') => Key::CtrlC, + TermionKey::Ctrl('d') => Key::CtrlD, + TermionKey::Ctrl('e') => Key::CtrlE, + TermionKey::Ctrl('f') => Key::CtrlF, + TermionKey::Ctrl('g') => Key::CtrlG, + TermionKey::Ctrl('h') => Key::CtrlH, + TermionKey::Ctrl('i') => Key::CtrlI, + TermionKey::Ctrl('j') => Key::CtrlJ, + TermionKey::Ctrl('k') => Key::CtrlK, + TermionKey::Ctrl('l') => Key::CtrlL, + TermionKey::Ctrl('m') => Key::CtrlM, + TermionKey::Ctrl('n') => Key::CtrlN, + TermionKey::Ctrl('o') => Key::CtrlO, + TermionKey::Ctrl('p') => Key::CtrlP, + TermionKey::Ctrl('q') => Key::CtrlQ, + TermionKey::Ctrl('r') => Key::CtrlR, + TermionKey::Ctrl('s') => Key::CtrlS, + TermionKey::Ctrl('t') => Key::CtrlT, + TermionKey::Ctrl('u') => Key::CtrlU, + TermionKey::Ctrl('v') => Key::CtrlV, + TermionKey::Ctrl('w') => Key::CtrlW, + TermionKey::Ctrl('x') => Key::CtrlX, + TermionKey::Ctrl('y') => Key::CtrlY, + TermionKey::Ctrl('z') => Key::CtrlZ, + + TermionKey::Alt('a') => Key::AltA, + TermionKey::Alt('b') => Key::AltB, + TermionKey::Alt('c') => Key::AltC, + TermionKey::Alt('d') => Key::AltD, + TermionKey::Alt('e') => Key::AltE, + TermionKey::Alt('f') => Key::AltF, + TermionKey::Alt('g') => Key::AltG, + TermionKey::Alt('h') => Key::AltH, + TermionKey::Alt('i') => Key::AltI, + TermionKey::Alt('j') => Key::AltJ, + TermionKey::Alt('k') => Key::AltK, + TermionKey::Alt('l') => Key::AltL, + TermionKey::Alt('m') => Key::AltM, + TermionKey::Alt('n') => Key::AltN, + TermionKey::Alt('o') => Key::AltO, + TermionKey::Alt('p') => Key::AltP, + TermionKey::Alt('q') => Key::AltQ, + TermionKey::Alt('r') => Key::AltR, + TermionKey::Alt('s') => Key::AltS, + TermionKey::Alt('t') => Key::AltT, + TermionKey::Alt('u') => Key::AltU, + TermionKey::Alt('v') => Key::AltV, + TermionKey::Alt('w') => Key::AltW, + TermionKey::Alt('x') => Key::AltX, + TermionKey::Alt('y') => Key::AltY, + TermionKey::Alt('z') => Key::AltZ, + + TermionKey::Char('A') => Key::ShiftA, + TermionKey::Char('B') => Key::ShiftB, + TermionKey::Char('C') => Key::ShiftC, + TermionKey::Char('D') => Key::ShiftD, + TermionKey::Char('E') => Key::ShiftE, + TermionKey::Char('F') => Key::ShiftF, + TermionKey::Char('G') => Key::ShiftG, + TermionKey::Char('H') => Key::ShiftH, + TermionKey::Char('I') => Key::ShiftI, + TermionKey::Char('J') => Key::ShiftJ, + TermionKey::Char('K') => Key::ShiftK, + TermionKey::Char('L') => Key::ShiftL, + TermionKey::Char('M') => Key::ShiftM, + TermionKey::Char('N') => Key::ShiftN, + TermionKey::Char('O') => Key::ShiftO, + TermionKey::Char('P') => Key::ShiftP, + TermionKey::Char('Q') => Key::ShiftQ, + TermionKey::Char('R') => Key::ShiftR, + TermionKey::Char('S') => Key::ShiftS, + TermionKey::Char('T') => Key::ShiftT, + TermionKey::Char('U') => Key::ShiftU, + TermionKey::Char('V') => Key::ShiftV, + TermionKey::Char('W') => Key::ShiftW, + TermionKey::Char('X') => Key::ShiftX, + TermionKey::Char('Y') => Key::ShiftY, + TermionKey::Char('Z') => Key::ShiftZ, + + TermionKey::Ctrl('A') => Key::CtrlShiftA, + TermionKey::Ctrl('B') => Key::CtrlShiftB, + TermionKey::Ctrl('C') => Key::CtrlShiftC, + TermionKey::Ctrl('D') => Key::CtrlShiftD, + TermionKey::Ctrl('E') => Key::CtrlShiftE, + TermionKey::Ctrl('F') => Key::CtrlShiftF, + TermionKey::Ctrl('G') => Key::CtrlShiftG, + TermionKey::Ctrl('H') => Key::CtrlShiftH, + TermionKey::Ctrl('I') => Key::CtrlShiftI, + TermionKey::Ctrl('J') => Key::CtrlShiftJ, + TermionKey::Ctrl('K') => Key::CtrlShiftK, + TermionKey::Ctrl('L') => Key::CtrlShiftL, + TermionKey::Ctrl('M') => Key::CtrlShiftM, + TermionKey::Ctrl('N') => Key::CtrlShiftN, + TermionKey::Ctrl('O') => Key::CtrlShiftO, + TermionKey::Ctrl('P') => Key::CtrlShiftP, + TermionKey::Ctrl('Q') => Key::CtrlShiftQ, + TermionKey::Ctrl('R') => Key::CtrlShiftR, + TermionKey::Ctrl('S') => Key::CtrlShiftS, + TermionKey::Ctrl('T') => Key::CtrlShiftT, + TermionKey::Ctrl('U') => Key::CtrlShiftU, + TermionKey::Ctrl('V') => Key::CtrlShiftV, + TermionKey::Ctrl('W') => Key::CtrlShiftW, + TermionKey::Ctrl('X') => Key::CtrlShiftX, + TermionKey::Ctrl('Y') => Key::CtrlShiftY, + TermionKey::Ctrl('Z') => Key::CtrlShiftZ, + + TermionKey::Alt('A') => Key::AltShiftA, + TermionKey::Alt('B') => Key::AltShiftB, + TermionKey::Alt('C') => Key::AltShiftC, + TermionKey::Alt('D') => Key::AltShiftD, + TermionKey::Alt('E') => Key::AltShiftE, + TermionKey::Alt('F') => Key::AltShiftF, + TermionKey::Alt('G') => Key::AltShiftG, + TermionKey::Alt('H') => Key::AltShiftH, + TermionKey::Alt('I') => Key::AltShiftI, + TermionKey::Alt('J') => Key::AltShiftJ, + TermionKey::Alt('K') => Key::AltShiftK, + TermionKey::Alt('L') => Key::AltShiftL, + TermionKey::Alt('M') => Key::AltShiftM, + TermionKey::Alt('N') => Key::AltShiftN, + TermionKey::Alt('O') => Key::AltShiftO, + TermionKey::Alt('P') => Key::AltShiftP, + TermionKey::Alt('Q') => Key::AltShiftQ, + TermionKey::Alt('R') => Key::AltShiftR, + TermionKey::Alt('S') => Key::AltShiftS, + TermionKey::Alt('T') => Key::AltShiftT, + TermionKey::Alt('U') => Key::AltShiftU, + TermionKey::Alt('V') => Key::AltShiftV, + TermionKey::Alt('W') => Key::AltShiftW, + TermionKey::Alt('X') => Key::AltShiftX, + TermionKey::Alt('Y') => Key::AltShiftY, + TermionKey::Alt('Z') => Key::AltShiftZ, + + TermionKey::Char('+') => Key::Plus, + TermionKey::Char('-') => Key::Minus, + TermionKey::Char('`') => Key::Backtick, + TermionKey::Char('~') => Key::Tilda, + TermionKey::Char('_') => Key::Underscore, + TermionKey::Char('=') => Key::Equals, + TermionKey::Char(';') => Key::Semicolon, + TermionKey::Char(':') => Key::Colon, + TermionKey::Char('\'') => Key::SingleQuote, + TermionKey::Char('"') => Key::DoubleQuote, + TermionKey::Char('/') => Key::ForwardSlash, + TermionKey::Char('\\') => Key::BackSlash, + TermionKey::Char('.') => Key::Dot, + TermionKey::Char(',') => Key::Comma, + TermionKey::Char('?') => Key::QuestionMark, + + _ => Key::NotSupported, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b891aa7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +pub mod ui; +pub mod input; +pub mod config; +pub mod app; +pub mod error; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2980323 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,61 @@ +use handlebars::Handlebars; +use std::io; +use termion::{input::MouseTerminal, input::TermRead, raw::IntoRawMode, screen::AlternateScreen}; +use tui::backend::CrosstermBackend; +use tui::widgets::{ListState, TableState}; +use tui::Terminal; +use xplr::app; +use xplr::error::Error; +use xplr::input::Key; +use xplr::ui; + +fn main() -> Result<(), Error> { + let mut app = app::create()?; + + let mut hb = Handlebars::new(); + hb.register_template_string( + app::TEMPLATE_TABLE_ROW, + &app.config + .clone() + .general + .table + .row + .cols + .iter() + .map(|c| c.format.to_string()) + .collect::>() + .join("\t"), + )?; + + let stdout = io::stdout().into_raw_mode()?; + let stdout = MouseTerminal::from(stdout); + let stdout = AlternateScreen::from(stdout); + let stdin = io::stdin(); + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let keys = stdin + .keys() + .map(|e| e.map_or(Key::NotSupported, |e| Key::from_termion_event(e))); + + let mut table_state = TableState::default(); + let mut list_state = ListState::default(); + + terminal.draw(|f| ui::draw(&app, &hb, f, &mut table_state, &mut list_state))?; + + 'outer: for key in keys { + if let Some(actions) = app.actions_from_key(key) { + for action in actions.iter() { + app = app.handle(action)?; + terminal.draw(|f| ui::draw(&app, &hb, f, &mut table_state, &mut list_state))?; + if app.result.is_some() { + break 'outer; + } + } + }; + } + + std::mem::drop(terminal); + println!("{}", app.result.unwrap_or("".into())); + Ok(()) +} diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..a16f512 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,120 @@ +use handlebars::Handlebars; +use tui::backend::Backend; +use tui::layout::{Constraint as TUIConstraint, Direction, Layout}; +use tui::widgets::{Block, Borders, Cell, List, ListItem, ListState, Row, Table, TableState}; +use tui::Frame; +use crate::app; + +pub fn draw( + app: &app::App, + hb: &Handlebars, + f: &mut Frame, + table_state: &mut TableState, + list_state: &mut ListState, +) { + let chunks = Layout::default() + .direction(Direction::Horizontal) + .margin(1) + .constraints([TUIConstraint::Percentage(70), TUIConstraint::Percentage(30)].as_ref()) + .split(f.size()); + + let body = app + .directory_buffer + .items + .iter() + .map(|(_, m)| { + let txt = hb + .render(app::TEMPLATE_TABLE_ROW, &m) + .ok() + .unwrap_or_else(|| app::UNSUPPORTED_STR.into()) + .split("\t") + .map(|x| Cell::from(x.to_string())) + .collect::>(); + + let style = if m.is_focused { + app.config.general.focused_ui.style + } else if m.is_selected { + app.config.general.selected_ui.style + } else { + app.config + .filetypes + .special + .get(&m.relative_path) + .or_else(|| app.config.filetypes.extension.get(&m.extension)) + .unwrap_or_else(|| { + if m.is_symlink { + &app.config.filetypes.symlink + } else if m.is_dir { + &app.config.filetypes.directory + } else { + &app.config.filetypes.file + } + }) + .style + }; + (txt, style) + }) + .map(|(t, s)| Row::new(t).style(s)) + .collect::>(); + + let table_constraints: Vec = app + .config + .general + .table + .col_widths + .clone() + .into_iter() + .map(|c| c.into()) + .collect(); + + let table = Table::new(body) + .widths(&table_constraints) + .style(app.config.general.table.style) + .highlight_style(app.config.general.focused_ui.style) + .column_spacing(app.config.general.table.col_spacing) + .block( + Block::default() + .borders(Borders::ALL) + .title(format!(" {} ", app.pwd.to_str().unwrap_or("???"))), + ); + + let table = app + .config + .general + .table + .header + .clone() + .map(|h| { + table.clone().header( + Row::new( + h.cols + .iter() + .map(|c| Cell::from(c.format.to_owned())) + .collect::>(), + ) + .height(h.height) + .style(h.style), + ) + }) + .unwrap_or_else(|| table.clone()); + + table_state.select( + app.directory_buffer + .focus + .map(app::DirectoryBuffer::relative_focus), + ); + f.render_stateful_widget(table, chunks[0], table_state); + + let selected: Vec = app + .selected_paths + .iter() + .map(|p| p.to_str().unwrap_or(app::UNSUPPORTED_STR)) + .map(String::from) + .map(ListItem::new) + .collect(); + + // Selected items + let selected_list = + List::new(selected).block(Block::default().borders(Borders::ALL).title(" Selected ")); + f.render_stateful_widget(selected_list, chunks[1], list_state); +} diff --git a/tests/test_input.rs b/tests/test_input.rs new file mode 100644 index 0000000..f0e648a --- /dev/null +++ b/tests/test_input.rs @@ -0,0 +1,16 @@ +use xplr::*; + +#[test] +fn test_key_down() { + let mut app = app::create().expect("failed to create app"); + + assert_eq!(app.directory_buffer.focus, Some(0)); + + let actions = app.actions_from_key(input::Key::Down).unwrap(); + + for action in actions { + app = app.handle(&action).unwrap() + } + + assert_eq!(app.directory_buffer.focus, Some(1)); +}