diff --git a/Cargo.lock b/Cargo.lock index 5f86cac..cb863a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,18 +16,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-tzdata" @@ -52,14 +52,12 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi-to-tui" -version = "4.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8438af3d7e7dccdb98eff55e5351587d9bec2294daff505fc9a061bd14d22db0" +checksum = "0b0e348dcd256ba06d44d5deabc88a7c0e80ee7303158253ca069bcd9e9b7f57" dependencies = [ "nom", "ratatui", - "simdutf8", - "smallvec", "thiserror", ] @@ -71,9 +69,9 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arrayvec" @@ -98,9 +96,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "beef" @@ -116,9 +114,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -136,9 +134,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cassowary" @@ -163,9 +161,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.90" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" [[package]] name = "cfg-if" @@ -175,16 +173,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -216,9 +214,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.3" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", ] @@ -357,7 +355,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "crossterm_winapi", "libc", "mio", @@ -503,9 +501,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "equivalent" @@ -559,9 +557,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -570,9 +568,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -580,9 +578,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -649,9 +647,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -660,9 +658,9 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "is-terminal" @@ -695,9 +693,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jf" @@ -726,9 +724,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libm" @@ -738,13 +736,12 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", - "redox_syscall", ] [[package]] @@ -755,9 +752,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -798,9 +795,9 @@ dependencies = [ [[package]] name = "luajit-src" -version = "210.5.7+d06beb0" +version = "210.5.8+5790d25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d251fdacdabbf87704cf48ac1f8b1eb23d6e10855c3ee08e5beb25b4be2e9e4" +checksum = "441f18d9ad792e871fc2f7f2cb8902c386f6f56fdbddef3b835b61475e375346" dependencies = [ "cc", "which", @@ -808,9 +805,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memoffset" @@ -857,9 +854,9 @@ dependencies = [ [[package]] name = "mlua" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "868d02cb5eb97761bbf6bd6922c1c7a88b8ea252bbf43bd8350a0bf8497a1fc0" +checksum = "6d9bed6bce296397a9d6a86f995dd10a547a4e6949825d45225906bdcbfe7367" dependencies = [ "bstr", "erased-serde", @@ -873,9 +870,9 @@ dependencies = [ [[package]] name = "mlua-sys" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2847b42764435201d8cbee1f517edb79c4cca4181877b90047587c89e1b7bce4" +checksum = "d16a9ba1dd2c6ac971b204262d434c24d65067038598f0638b64e5dca28d52b8" dependencies = [ "cc", "cfg-if", @@ -981,9 +978,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -991,15 +988,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1101,18 +1098,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1123,7 +1120,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cassowary", "compact_str", "crossterm", @@ -1140,9 +1137,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -1160,18 +1157,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -1180,9 +1177,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1203,9 +1200,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustc-hash" @@ -1215,11 +1212,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1228,9 +1225,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" @@ -1255,9 +1252,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] @@ -1274,20 +1271,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -1296,9 +1293,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.33" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", @@ -1330,19 +1327,13 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] -[[package]] -name = "simdutf8" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" - [[package]] name = "skim" version = "0.10.4" @@ -1370,9 +1361,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" @@ -1431,7 +1422,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.53", + "syn 2.0.60", ] [[package]] @@ -1447,9 +1438,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.53" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -1486,22 +1477,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", ] [[package]] @@ -1516,9 +1507,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -1539,9 +1530,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -1620,9 +1611,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode_categories" @@ -1715,7 +1706,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", "wasm-bindgen-shared", ] @@ -1737,7 +1728,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1760,15 +1751,14 @@ dependencies = [ [[package]] name = "which" -version = "6.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", "rustix", - "windows-sys 0.52.0", + "winsafe", ] [[package]] @@ -1789,11 +1779,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -1808,7 +1798,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1826,7 +1816,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1846,17 +1836,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1867,9 +1858,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -1879,9 +1870,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1891,9 +1882,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1903,9 +1900,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1915,9 +1912,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1927,9 +1924,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1939,9 +1936,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winsafe" +version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "xdg" @@ -1951,7 +1954,7 @@ checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "xplr" -version = "0.21.7" +version = "0.21.8" dependencies = [ "ansi-to-tui", "anyhow", @@ -2002,5 +2005,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", ] diff --git a/Cargo.toml b/Cargo.toml index 6c50de0..7aa1739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ path = './benches/criterion.rs' [package] name = 'xplr' -version = '0.21.7' +version = '0.21.8' authors = ['Arijit Basu '] edition = '2021' description = 'A hackable, minimal, fast TUI file explorer' @@ -28,7 +28,7 @@ natord = "1.0.9" anyhow = "1.0.81" serde_yaml = "0.9.33" crossterm = { version = "0.27.0", features = [], default-features = false } -ansi-to-tui = "4.0.1" +ansi-to-tui = "3.1.0" regex = "1.10.3" gethostname = "0.4.3" serde_json = "1.0.114" @@ -57,7 +57,7 @@ version = "2.0.4" default-features = false [dependencies.tui] -version = "0.26.1" +version = "=0.26.1" # https://github.com/ratatui-org/ratatui/issues/1032 default-features = false features = ['crossterm', 'serde'] package = 'ratatui' @@ -73,7 +73,7 @@ features = ['serde'] [dependencies.mlua] version = "0.9.6" -features = ['luajit', 'vendored', 'serialize', 'send'] +features = ['luajit', 'serialize', 'send'] [dependencies.tui-input] version = "0.8.0" @@ -90,5 +90,5 @@ panic = 'abort' strip = true [features] - - +default = ["vendored-lua"] +vendored-lua = ["mlua/vendored"] diff --git a/README.md b/README.md index 2d42ca6..9758bc6 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,6 @@ A hackable, minimal, fast TUI file explorer - - - - - -Matrix - - - - - -

@@ -38,7 +26,6 @@ https://user-images.githubusercontent.com/11632726/166747867-8a4573f2-cb2f-43a6- [Hacks] [Plugins] [Integrations] - [Community] xplr is a terminal UI based file explorer that aims to increase our terminal @@ -60,7 +47,7 @@ integration][15], enabling you to achieve insane terminal productivity. - [[Article] What is a TUI file explorer & why would you need one? ~ xplr.stck.me](https://xplr.stck.me/post/25252/What-is-a-TUI-file-explorer-why-would-you-need-one) -- [[Article] FOSSPicks - Linux Magazine](https://www.linux-magazine.com/Issues/2022/258/FOSSPicks/(offset)/6) +- [[Article] FOSSPicks - Linux Magazine]() ## Packaging diff --git a/RELEASE.md b/RELEASE.md index 8f30cda..75bdd3e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,8 +3,8 @@ See [install.md](./docs/en/src/install.md#build-from-source) Note: xplr ships with vendored luajit. If the platform can't compile this, -you need to grep out the feature "vendored" from the "mlua" dependency -specified in [Cargo.toml](./Cargo.toml), and static link luajit yourself. +you need to compile using `--no-default-features` argument to avoid using +vendored luajit, so that you can static link luajit yourself. # Release diff --git a/benches/criterion.rs b/benches/criterion.rs index 04d31a4..cff705b 100644 --- a/benches/criterion.rs +++ b/benches/criterion.rs @@ -98,6 +98,7 @@ fn draw_benchmark(c: &mut Criterion) { }); let lua = mlua::Lua::new(); + let mut ui = ui::UI::new(&lua); let mut app = app::App::create("xplr".into(), None, PWD.into(), &lua, None, [].into()) .expect("failed to create app"); @@ -121,7 +122,7 @@ fn draw_benchmark(c: &mut Criterion) { c.bench_function("draw on terminal", |b| { b.iter(|| { - terminal.draw(|f| ui::draw(f, &app, &lua)).unwrap(); + terminal.draw(|f| ui.draw(f, &app)).unwrap(); }) }); diff --git a/docs/en/src/SUMMARY.md b/docs/en/src/SUMMARY.md index 210136c..2087022 100644 --- a/docs/en/src/SUMMARY.md +++ b/docs/en/src/SUMMARY.md @@ -39,8 +39,6 @@ - [Awesome Integrations][20] - [Alternatives][22] - [Upgrade Guide][23] -- [Community][24] -- [Contribute][25] [1]: introduction.md [2]: quickstart.md @@ -64,8 +62,6 @@ [20]: awesome-integrations.md [22]: alternatives.md [23]: upgrade-guide.md -[24]: community.md -[25]: contribute.md [26]: column-renderer.md [27]: key-bindings.md [28]: configure-key-bindings.md diff --git a/docs/en/src/alternatives.md b/docs/en/src/alternatives.md index 273e9d8..0fe9c8c 100644 --- a/docs/en/src/alternatives.md +++ b/docs/en/src/alternatives.md @@ -15,6 +15,7 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec - [clifm][11] - [clifm][12] (non curses) - [felix][14] +- [yazi][15] [add more][13] @@ -30,5 +31,6 @@ These are the alternative TUI/CLI file managers/explorers you might want to chec [10]: https://git.2f30.org/noice/ [11]: https://github.com/pasqu4le/clifm [12]: https://github.com/leo-arch/clifm -[13]: community.md +[13]: https://github.com/sayanarijit/xplr/edit/dev/docs/en/src/alternatives.md [14]: https://github.com/kyoheiu/felix +[15]: https://github.com/sxyazi/yazi diff --git a/docs/en/src/awesome-hacks.md b/docs/en/src/awesome-hacks.md index fa4be46..8cfb030 100644 --- a/docs/en/src/awesome-hacks.md +++ b/docs/en/src/awesome-hacks.md @@ -6,7 +6,7 @@ too small or too niche for a full fledge [plugin][2]. Do you have something cool to share? -[Edit this file][3] or [share them here][4] or [let us know][5]. +[Edit this file][3] or [share them here][4]. You can try these hacks by writing them to a file, say `hack.lua` and passing it to xplr with `--extra-config` or `-C`. @@ -526,7 +526,6 @@ xplr.config.modes.builtin.default.key_bindings.on_key.T = { [2]: plugin.md [3]: https://github.com/sayanarijit/xplr/edit/main/docs/en/src/awesome-hacks.md [4]: https://github.com/sayanarijit/xplr/discussions/categories/show-and-tell -[5]: community.md [6]: https://gifyu.com/image/rGSR [7]: https://s4.gifyu.com/images/xplr-bookmark.gif [8]: https://github.com/sayanarijit diff --git a/docs/en/src/community.md b/docs/en/src/community.md deleted file mode 100644 index a280b7a..0000000 --- a/docs/en/src/community.md +++ /dev/null @@ -1,12 +0,0 @@ -# Community - -Building an active community of awesome people and learning stuff together is -one of my reasons to publish this tool and maintain it. Hence, please feel free -to reach out via your preferred way. - -- Real-time chat lovers can join our [**matrix room**][3] or [**discord channel**][1]. -- Forum discussion veterans can [**start a new GitHub discussion**][2]. - -[1]: https://discord.gg/JmasSPCcz3 -[2]: https://github.com/sayanarijit/xplr/discussions -[3]: https://matrix.to/#/#xplr-pub:matrix.org diff --git a/docs/en/src/contribute.md b/docs/en/src/contribute.md deleted file mode 100644 index a01ddba..0000000 --- a/docs/en/src/contribute.md +++ /dev/null @@ -1,32 +0,0 @@ -If you like xplr, and want to contribute, that would be really awesome. - -You can contribute to this project in the following ways - -- Contribute your time and expertise (read [CONTRIBUTING.md][1] for instructions). - - - **Developers:** You can help me improve my code, fix things, implement features etc. - - **Repository maintainers:** You can save the users from the pain of managing xplr in their system manually. - - **Code Reviewers:** Teach me your ways of code. - - **Designers:** You can make the logo even more awesome, donate stickers and blog post worthy pictures. - - **Bloggers, YouTubers & broadcasters:** You can help spread the word. - -- Contribute by donating or sponsoring me via any of the following ways. - - [GitHub Sponsors][5] - - [Open Collective][2] - - [ko-fi][3] - - [liberapay][6] - - [PayPal][7] - -For further queries or concern related to `xplr`, [just ask us][4]. - -### Backers - - - -[1]: https://github.com/sayanarijit/xplr/blob/main/CONTRIBUTING.md -[2]: https://opencollective.com/xplr -[3]: https://ko-fi.com/sayanarijit -[4]: community.md -[5]: https://github.com/sponsors/sayanarijit?o=esb -[6]: https://liberapay.com/sayanarijit -[7]: https://paypal.me/sayanarijit diff --git a/docs/en/src/general-config.md b/docs/en/src/general-config.md index 75fa861..b559f3e 100644 --- a/docs/en/src/general-config.md +++ b/docs/en/src/general-config.md @@ -42,6 +42,19 @@ Set it to `true` if you want to hide all remaps in the help menu. Type: boolean +#### xplr.config.general.paginated_scrolling + +Set it to `true` if you want paginated scrolling. + +Type: boolean + +#### xplr.config.general.scroll_padding + +Set the padding value to the scroll area. +Only applicable when `xplr.config.general.paginated_scrolling = false`. + +Type: boolean + #### xplr.config.general.enforce_bounded_index_navigation Set it to `true` if you want the cursor to stay in the same position when diff --git a/docs/en/src/install.md b/docs/en/src/install.md index 114e7ee..518247e 100644 --- a/docs/en/src/install.md +++ b/docs/en/src/install.md @@ -238,23 +238,6 @@ cargo build --locked --release --bin xplr sudo cp target/release/xplr /usr/local/bin/ ``` -## Android - -### [Termux][24] - -```bash -pkg install rust make binutils -cargo install --locked xplr - -# Run -~/.cargo/bin/xplr -``` - -> Please note that xplr isn't heavily tested on Termux, hence things might need -> a little tweaking and fixing for a smooth user experience. - -![termux demo][23] - [1]: #direct-download [2]: #from-cratesio [3]: #build-from-source @@ -278,7 +261,6 @@ cargo install --locked xplr [21]: https://www.gnu.org/software/make/ [22]: https://git-scm.com/ [23]: https://github.com/sayanarijit/xplr/assets/11632726/3b61e8c8-76f0-48e8-8734-50e9e7e495b7 -[24]: https://termux.dev/ [25]: https://gifyu.com/image/tF2D [26]: https://github.com/sayanarijit/xplr/releases/latest/download/xplr-linux-musl.tar.gz [27]: https://pkgs.alpinelinux.org/packages?name=xplr diff --git a/docs/en/src/introduction.md b/docs/en/src/introduction.md index d48d8ce..5e68489 100644 --- a/docs/en/src/introduction.md +++ b/docs/en/src/introduction.md @@ -84,8 +84,6 @@ Some of the coolest features xplr provide beside the basic stuff: (`:` `q` `s`). - Quit with failure (`ctrl-c`). -**Q.** What features should be added here? [let us know][20]. - [1]: layouts.md [2]: configure-key-bindings.md [3]: awesome-plugins.md @@ -105,6 +103,5 @@ Some of the coolest features xplr provide beside the basic stuff: [17]: node_types.md [18]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua [19]: messages.md#startfifo -[20]: community.md [21]: messages.md#virtual-root [22]: configuration.md#hooks diff --git a/docs/en/src/layout.md b/docs/en/src/layout.md index ec603a8..a053193 100644 --- a/docs/en/src/layout.md +++ b/docs/en/src/layout.md @@ -495,6 +495,7 @@ It contains the following information: - [layout_size][37] - [screen_size][37] +- [scrolltop][57] - [app][38] ### Size @@ -508,6 +509,12 @@ It contains the following information: Every field is of integer type. +### scrolltop + +Type: integer + +The start index of the visible nodes in the table. + ### app This is a lightweight version of the [Lua Context][39]. In this context, the @@ -587,3 +594,4 @@ Hence, only the following fields are available. [54]: borders.md#border-type [55]: #customlayout [56]: sum-type.md +[57]: #scrolltop diff --git a/docs/en/src/sum-type.md b/docs/en/src/sum-type.md index 26e93dd..f7df1a5 100644 --- a/docs/en/src/sum-type.md +++ b/docs/en/src/sum-type.md @@ -90,13 +90,7 @@ And then we'd go on documenting whatever `Layout Config` is. So, there you go. This is exactly what sum types are - glorified enums that can have nested types in each branch. ---- - -If you're still confused about something, or if you found an error in this -explanation, feel free to [discuss together][5]. - [1]: https://en.wikipedia.org/wiki/Tagged_union [2]: layout.md [3]: message.md [4]: style.md#color -[5]: community.md diff --git a/docs/en/src/upgrade-guide.md b/docs/en/src/upgrade-guide.md index 54bb05c..511b965 100644 --- a/docs/en/src/upgrade-guide.md +++ b/docs/en/src/upgrade-guide.md @@ -45,7 +45,7 @@ compatibility. ### Instructions -#### [v0.20.2][48] -> [v0.21.7][49] +#### [v0.20.2][48] -> [v0.21.8][49] - Some plugins might stop rendering colors. Wait for them to update. - Rename `xplr.config.general.sort_and_filter_ui.search_identifier` to @@ -127,6 +127,13 @@ compatibility. - You can use `c` and `m` keys in default mode to quickly copy and move focused or selected files, without having to change directory. - Use `xplr.util.debug()` to debug lua values. +- Since v0.21.8: + - Scroll behavior will default to vim-like continuous scrolling. You can set + `xplr.config.general.paginated_scrolling = true` to revert back to the + paginated scrolling. + - Set `xplr.config.general.scroll_padding` to customize the scroll padding. + - The calculated `scrolltop` value will be passed as part of the + `Content Rendeder Argument` in `Dynamic` layout renderer functions. Thanks to @noahmayr for contributing to a major part of this release. @@ -214,8 +221,6 @@ Thanks to @noahmayr for contributing to a major part of this release. - ScrollUpHalf ---- { - ScrollDownHalf -- } -Like this project so far? **[Please consider contributing][5]**. - #### [v0.17.6][45] -> [v0.18.0][46] - Key binding `f` `r` and `f` `R` will now filter using regex. @@ -481,7 +486,6 @@ Else do the following: [2]: https://github.com/sayanarijit/xplr/releases/tag/v0.13.7 [3]: https://github.com/sayanarijit/xplr/releases/tag/v0.14.7 [4]: https://github.com/sayanarijit/xplr/pull/229#issue-662426960 -[5]: contribute.md [6]: https://github.com/sayanarijit/xplr/releases/tag/v0.12.1 [7]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLua [8]: https://docs.rs/xplr/latest/xplr/app/enum.ExternalMsg.html#variant.CallLuaSilently @@ -525,5 +529,5 @@ Else do the following: [46]: https://github.com/sayanarijit/xplr/releases/tag/v0.18.0 [47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.4 [48]: https://github.com/sayanarijit/xplr/releases/tag/v0.20.2 -[49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.7 +[49]: https://github.com/sayanarijit/xplr/releases/tag/v0.21.8 [50]: https://github.com/lotabout/skim#search-syntax diff --git a/docs/en/src/writing-plugins.md b/docs/en/src/writing-plugins.md index 21786e3..e56e038 100644 --- a/docs/en/src/writing-plugins.md +++ b/docs/en/src/writing-plugins.md @@ -70,8 +70,6 @@ Visit [Awesome Plugins][5] for xplr plugin examples. - [Tip: A list of hacks yet to make it as Lua plugins][15] - [Tip: Some UI and themeing tips][12] -- [Tip: A list of handy utility functions][13] -- [Tip: Share tips and tricks working with Lua][14] - [Tutorial: Adding a New Mode][6] - [Example: Using Environment Variables and Pipes][7] - [Example: Using Lua Function Calls][8] @@ -92,7 +90,5 @@ Visit [Awesome Plugins][5] for xplr plugin examples. [10]: column-renderer.md#example-customizing-table-renderer [11]: layout.md#example-render-a-custom-dynamic-table [12]: https://github.com/sayanarijit/xplr/discussions/274 -[13]: https://github.com/sayanarijit/xplr/discussions/273 -[14]: https://github.com/sayanarijit/xplr/discussions/250 -[15]: https://github.com/sayanarijit/xplr/wiki/Hacks +[15]: awesome-hacks.md [16]: https://github.com/sayanarijit/xplr/discussions/529#discussioncomment-4073734 diff --git a/docs/en/src/xplr.util.md b/docs/en/src/xplr.util.md index fed14d0..7ef601c 100644 --- a/docs/en/src/xplr.util.md +++ b/docs/en/src/xplr.util.md @@ -397,7 +397,7 @@ xplr.util.to_yaml({ foo = "bar" }) Get a [Style][3] object for the given path based on the LS_COLORS environment variable. -Type: function( path:string ) -> [Style][3]|nil +Type: function( path:string ) -> [Style][3] Example: diff --git a/docs/script/deploy-cf.sh b/docs/script/deploy-cf.sh new file mode 100644 index 0000000..1acd71a --- /dev/null +++ b/docs/script/deploy-cf.sh @@ -0,0 +1,11 @@ +v="0.4.37" + +curl -L https://github.com/rust-lang/mdBook/releases/download/v$v/mdbook-v$v-x86_64-unknown-linux-gnu.tar.gz -o mdbook.tgz \ + && tar xzvf mdbook.tgz \ + && ./mdbook build docs/en \ + && mkdir dist \ + && mv -v docs/en/book/html dist/en \ + && mv -v assets dist \ + && mv -v docs/landing/index.html docs/landing/css docs/landing/js dist \ + && rm -v mdbook \ + && rm -v mdbook.tgz diff --git a/src/app.rs b/src/app.rs index 49f3b33..e4700df 100644 --- a/src/app.rs +++ b/src/app.rs @@ -335,12 +335,12 @@ impl App { &config .general .initial_mode - .to_owned() + .clone() .unwrap_or_else(|| "default".into()), ) { Some(m) => m.clone().sanitized( config.general.read_only, - config.general.global_key_bindings.to_owned(), + config.general.global_key_bindings.clone(), ), None => { bail!("'default' mode is missing") @@ -351,7 +351,7 @@ impl App { &config .general .initial_layout - .to_owned() + .clone() .unwrap_or_else(|| "default".into()), ) { Some(l) => l.clone(), @@ -387,7 +387,7 @@ impl App { } if let Some(sorters) = &config.general.initial_sorting { - explorer_config.sorters = sorters.clone(); + explorer_config.sorters.clone_from(sorters); }; let hostname = gethostname().to_string_lossy().to_string(); @@ -820,7 +820,6 @@ impl App { fn focus_previous(mut self) -> Result { let bounded = self.config.general.enforce_bounded_index_navigation; - if let Some(dir) = self.directory_buffer_mut() { dir.focus = if dir.focus == 0 { if bounded { @@ -905,7 +904,6 @@ impl App { fn focus_next(mut self) -> Result { let bounded = self.config.general.enforce_bounded_index_navigation; - if let Some(dir) = self.directory_buffer_mut() { dir.focus = if (dir.focus + 1) == dir.total { if bounded { @@ -917,6 +915,7 @@ impl App { dir.focus + 1 } }; + Ok(self) } @@ -995,7 +994,7 @@ impl App { fn follow_symlink(self) -> Result { if let Some(pth) = self .focused_node() - .and_then(|n| n.symlink.to_owned().map(|s| s.absolute_path)) + .and_then(|n| n.symlink.clone().map(|s| s.absolute_path)) { self.focus_path(&pth, true) } else { @@ -1382,7 +1381,7 @@ impl App { self = self.push_mode(); self.mode = mode.sanitized( self.config.general.read_only, - self.config.general.global_key_bindings.to_owned(), + self.config.general.global_key_bindings.clone(), ); // Hooks @@ -1407,7 +1406,7 @@ impl App { self = self.push_mode(); self.mode = mode.sanitized( self.config.general.read_only, - self.config.general.global_key_bindings.to_owned(), + self.config.general.global_key_bindings.clone(), ); // Hooks @@ -1434,7 +1433,7 @@ impl App { fn switch_layout_builtin(mut self, layout: &str) -> Result { if let Some(l) = self.config.layouts.builtin.get(layout) { - self.layout = l.to_owned(); + self.layout = l.clone(); // Hooks if !self.hooks.on_layout_switch.is_empty() { @@ -1450,7 +1449,7 @@ impl App { fn switch_layout_custom(mut self, layout: &str) -> Result { if let Some(l) = self.config.layouts.get_custom(layout) { - self.layout = l.to_owned(); + self.layout = l.clone(); // Hooks if !self.hooks.on_layout_switch.is_empty() { @@ -1575,7 +1574,7 @@ impl App { pub fn select(mut self) -> Result { let count = self.selection.len(); - if let Some(n) = self.focused_node().map(|n| n.to_owned()) { + if let Some(n) = self.focused_node().cloned() { self.selection.insert(n); } @@ -1630,7 +1629,7 @@ impl App { pub fn un_select(mut self) -> Result { let count = self.selection.len(); - if let Some(n) = self.focused_node().map(|n| n.to_owned()) { + if let Some(n) = self.focused_node().cloned() { self.selection .retain(|s| s.absolute_path != n.absolute_path); } @@ -1804,7 +1803,7 @@ impl App { .config .general .initial_sorting - .to_owned() + .clone() .unwrap_or_default(); Ok(self) } diff --git a/src/compat.rs b/src/compat.rs index bafd50c..469777c 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -7,7 +7,7 @@ use crate::ui::block; use crate::ui::string_to_text; use crate::ui::Constraint; use crate::ui::ContentRendererArg; -use mlua::Lua; +use crate::ui::UI; use serde::{Deserialize, Serialize}; use tui::layout::Constraint as TuiConstraint; use tui::layout::Rect as TuiRect; @@ -60,12 +60,11 @@ pub struct CustomContent { /// A cursed function from crate::ui. pub fn draw_custom_content( + ui: &mut UI, f: &mut Frame, - screen_size: TuiRect, layout_size: TuiRect, app: &app::App, content: CustomContent, - lua: &Lua, ) { let config = app.config.general.panel_ui.default.clone(); let title = content.title; @@ -85,12 +84,13 @@ pub fn draw_custom_content( let ctx = ContentRendererArg { app: app.to_lua_ctx_light(), layout_size: layout_size.into(), - screen_size: screen_size.into(), + screen_size: ui.screen_size.into(), + scrolltop: ui.scrolltop as u16, }; - let render = lua::serialize(lua, &ctx) + let render = lua::serialize(ui.lua, &ctx) .map(|arg| { - lua::call(lua, &render, arg).unwrap_or_else(|e| format!("{e:?}")) + lua::call(ui.lua, &render, arg).unwrap_or_else(|e| format!("{e:?}")) }) .unwrap_or_else(|e| e.to_string()); @@ -121,12 +121,13 @@ pub fn draw_custom_content( let ctx = ContentRendererArg { app: app.to_lua_ctx_light(), layout_size: layout_size.into(), - screen_size: screen_size.into(), + screen_size: ui.screen_size.into(), + scrolltop: ui.scrolltop as u16, }; - let items = lua::serialize(lua, &ctx) + let items = lua::serialize(ui.lua, &ctx) .map(|arg| { - lua::call(lua, &render, arg) + lua::call(ui.lua, &render, arg) .unwrap_or_else(|e| vec![format!("{e:?}")]) }) .unwrap_or_else(|e| vec![e.to_string()]) @@ -161,7 +162,7 @@ pub fn draw_custom_content( let widths = widths .into_iter() - .map(|w| w.to_tui(screen_size, layout_size)) + .map(|w| w.to_tui(ui.screen_size, layout_size)) .collect::>(); let content = Table::new(rows, widths) @@ -182,12 +183,13 @@ pub fn draw_custom_content( let ctx = ContentRendererArg { app: app.to_lua_ctx_light(), layout_size: layout_size.into(), - screen_size: screen_size.into(), + screen_size: ui.screen_size.into(), + scrolltop: ui.scrolltop as u16, }; - let rows = lua::serialize(lua, &ctx) + let rows = lua::serialize(ui.lua, &ctx) .map(|arg| { - lua::call(lua, &render, arg) + lua::call(ui.lua, &render, arg) .unwrap_or_else(|e| vec![vec![format!("{e:?}")]]) }) .unwrap_or_else(|e| vec![vec![e.to_string()]]) @@ -204,7 +206,7 @@ pub fn draw_custom_content( let widths = widths .into_iter() - .map(|w| w.to_tui(screen_size, layout_size)) + .map(|w| w.to_tui(ui.screen_size, layout_size)) .collect::>(); let mut content = Table::new(rows, &widths).block(block( diff --git a/src/config.rs b/src/config.rs index db08f86..5f34a32 100644 --- a/src/config.rs +++ b/src/config.rs @@ -55,7 +55,7 @@ pub struct NodeTypeConfig { impl NodeTypeConfig { pub fn extend(mut self, other: &Self) -> Self { self.style = self.style.extend(&other.style); - self.meta.extend(other.meta.to_owned()); + self.meta.extend(other.meta.clone()); self } } @@ -85,11 +85,11 @@ pub struct NodeTypesConfig { impl NodeTypesConfig { pub fn get(&self, node: &Node) -> NodeTypeConfig { let mut node_type = if node.is_symlink { - self.symlink.to_owned() + self.symlink.clone() } else if node.is_dir { - self.directory.to_owned() + self.directory.clone() } else { - self.file.to_owned() + self.file.clone() }; let mut me = node.mime_essence.splitn(2, '/'); @@ -104,7 +104,7 @@ impl NodeTypesConfig { node_type = node_type.extend(conf); } - if let Some(conf) = self.extension.get(&node.extension) { + if let (Some(conf), false) = (self.extension.get(&node.extension), node.is_dir) { node_type = node_type.extend(conf); } @@ -141,7 +141,7 @@ pub struct UiElement { impl UiElement { pub fn extend(mut self, other: &Self) -> Self { - self.format = other.format.to_owned().or(self.format); + self.format = other.format.clone().or(self.format); self.style = self.style.extend(&other.style); self } @@ -353,6 +353,12 @@ pub struct GeneralConfig { #[serde(default)] pub global_key_bindings: KeyBindings, + + #[serde(default)] + pub paginated_scrolling: bool, + + #[serde(default)] + pub scroll_padding: usize, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -638,8 +644,8 @@ impl PanelUiConfig { pub fn extend(mut self, other: &Self) -> Self { self.title = self.title.extend(&other.title); self.style = self.style.extend(&other.style); - self.borders = other.borders.to_owned().or(self.borders); - self.border_type = other.border_type.to_owned().or(self.border_type); + self.borders = other.borders.clone().or(self.borders); + self.border_type = other.border_type.or(self.border_type); self.border_style = self.border_style.extend(&other.border_style); self } diff --git a/src/dirs.rs b/src/dirs.rs index 4bbad82..60a7eca 100644 --- a/src/dirs.rs +++ b/src/dirs.rs @@ -22,5 +22,5 @@ pub fn runtime_dir() -> PathBuf { else { return env::temp_dir(); }; - dir.to_owned() + dir.clone() } diff --git a/src/init.lua b/src/init.lua index a36ddb7..e5b93c9 100644 --- a/src/init.lua +++ b/src/init.lua @@ -91,6 +91,17 @@ xplr.config.general.enable_recover_mode = false -- Type: boolean xplr.config.general.hide_remaps_in_help_menu = false +-- Set it to `true` if you want paginated scrolling. +-- +-- Type: boolean +xplr.config.general.paginated_scrolling = false + +-- Set the padding value to the scroll area. +-- Only applicable when `xplr.config.general.paginated_scrolling = false`. +-- +-- Type: boolean +xplr.config.general.scroll_padding = 5 + -- Set it to `true` if you want the cursor to stay in the same position when -- the focus is on the first path and you navigate to the previous path -- (by pressing `up`/`k`), or when the focus is on the last path and you @@ -478,7 +489,7 @@ xplr.config.general.sort_and_filter_ui.search_identifiers = { -- -- Type: nullable string xplr.config.general.sort_and_filter_ui.search_direction_identifiers.ordered.format = -"↓" + "↓" -- The shape of unordered indicator for search ordering identifiers in Sort & filter panel. -- @@ -676,7 +687,7 @@ xplr.config.general.panel_ui.sort_and_filter.border_style = {} -- Type: nullable list of [Node Sorter](https://xplr.dev/en/sorting#node-sorter-applicable) xplr.config.general.initial_sorting = { { sorter = "ByCanonicalIsDir", reverse = true }, - { sorter = "ByIRelativePath", reverse = false }, + { sorter = "ByIRelativePath", reverse = false }, } -- The name of one of the modes to use when xplr loads. @@ -1325,23 +1336,23 @@ xplr.config.modes.builtin.default = { } xplr.config.modes.builtin.default.key_bindings.on_key["v"] = - xplr.config.modes.builtin.default.key_bindings.on_key["space"] + xplr.config.modes.builtin.default.key_bindings.on_key["space"] xplr.config.modes.builtin.default.key_bindings.on_key["V"] = - xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-a"] + xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-a"] xplr.config.modes.builtin.default.key_bindings.on_key["/"] = - xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-f"] + xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-f"] xplr.config.modes.builtin.default.key_bindings.on_key["h"] = - xplr.config.modes.builtin.default.key_bindings.on_key["left"] + xplr.config.modes.builtin.default.key_bindings.on_key["left"] xplr.config.modes.builtin.default.key_bindings.on_key["j"] = - xplr.config.modes.builtin.default.key_bindings.on_key["down"] + xplr.config.modes.builtin.default.key_bindings.on_key["down"] xplr.config.modes.builtin.default.key_bindings.on_key["k"] = - xplr.config.modes.builtin.default.key_bindings.on_key["up"] + xplr.config.modes.builtin.default.key_bindings.on_key["up"] xplr.config.modes.builtin.default.key_bindings.on_key["l"] = - xplr.config.modes.builtin.default.key_bindings.on_key["right"] + xplr.config.modes.builtin.default.key_bindings.on_key["right"] xplr.config.modes.builtin.default.key_bindings.on_key["tab"] = - xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-i"] -- compatibility workaround + xplr.config.modes.builtin.default.key_bindings.on_key["ctrl-i"] -- compatibility workaround xplr.config.modes.builtin.default.key_bindings.on_key["?"] = - xplr.config.general.global_key_bindings.on_key["f1"] + xplr.config.general.global_key_bindings.on_key["f1"] -- The builtin debug error mode. -- @@ -1993,9 +2004,9 @@ xplr.config.modes.builtin.number = { } xplr.config.modes.builtin.number.key_bindings.on_key["j"] = - xplr.config.modes.builtin.number.key_bindings.on_key["down"] + xplr.config.modes.builtin.number.key_bindings.on_key["down"] xplr.config.modes.builtin.number.key_bindings.on_key["k"] = - xplr.config.modes.builtin.number.key_bindings.on_key["up"] + xplr.config.modes.builtin.number.key_bindings.on_key["up"] -- The builtin go to mode. -- @@ -2479,9 +2490,9 @@ xplr.config.modes.builtin.search = { } xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-n"] = - xplr.config.modes.builtin.search.key_bindings.on_key["down"] + xplr.config.modes.builtin.search.key_bindings.on_key["down"] xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-p"] = - xplr.config.modes.builtin.search.key_bindings.on_key["up"] + xplr.config.modes.builtin.search.key_bindings.on_key["up"] -- The builtin filter mode. -- @@ -3113,8 +3124,8 @@ xplr.fn.builtin.fmt_general_selection_item = function(n) if n.is_dir then shortened = shortened .. "/" end - local ls_style = xplr.util.lscolor(n.absolute_path) local meta_style = xplr.util.node_type(n).style + local ls_style = xplr.util.lscolor(n.absolute_path) local style = xplr.util.style_mix({ ls_style, meta_style }) return xplr.util.paint(shortened:gsub("\n", nl), style) end @@ -3137,8 +3148,8 @@ end xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m) local nl = xplr.util.paint("\\n", { add_modifiers = { "Italic", "Dim" } }) local r = m.tree .. m.prefix - local style = xplr.util.lscolor(m.absolute_path) - style = xplr.util.style_mix({ style, m.style }) + local ls_style = xplr.util.lscolor(m.absolute_path) + local style = xplr.util.style_mix({ ls_style, m.style }) if m.meta.icon == nil then r = r .. "" @@ -3161,7 +3172,7 @@ xplr.fn.builtin.fmt_general_table_row_cols_1 = function(m) r = r .. "×" else local symlink_path = - xplr.util.shorten(m.symlink.absolute_path, { base = m.parent }) + xplr.util.shorten(m.symlink.absolute_path, { base = m.parent }) if m.symlink.is_dir then symlink_path = symlink_path .. "/" end @@ -3183,14 +3194,14 @@ xplr.fn.builtin.fmt_general_table_row_cols_2 = function(m) local T = xplr.util.paint("T", { fg = "Red" }) return xplr.util - .permissions_rwx(m.permissions) - :gsub("r", r) - :gsub("w", w) - :gsub("x", x) - :gsub("s", s) - :gsub("S", S) - :gsub("t", t) - :gsub("T", T) + .permissions_rwx(m.permissions) + :gsub("r", r) + :gsub("w", w) + :gsub("x", x) + :gsub("s", s) + :gsub("S", S) + :gsub("t", t) + :gsub("T", T) end -- Renders the fourth column in the table diff --git a/src/input.rs b/src/input.rs index dd1fba5..b9f9115 100644 --- a/src/input.rs +++ b/src/input.rs @@ -647,7 +647,7 @@ impl Key { Self::ShiftZ => Some('Z'), Self::Space => Some(' '), - Self::Special(c) => Some(c.to_owned()), + Self::Special(c) => Some(*c), _ => None, } diff --git a/src/lua/mod.rs b/src/lua/mod.rs index 49c2640..467a3bf 100644 --- a/src/lua/mod.rs +++ b/src/lua/mod.rs @@ -160,24 +160,24 @@ mod tests { assert!(check_version(VERSION, "foo path").is_ok()); // Current release if OK - assert!(check_version("0.21.7", "foo path").is_ok()); + assert!(check_version("0.21.8", "foo path").is_ok()); // Prev major release is ERR // - Not yet // Prev minor release is ERR (Change when we get to v1) - assert!(check_version("0.20.7", "foo path").is_err()); + assert!(check_version("0.20.8", "foo path").is_err()); // Prev bugfix release is OK - assert!(check_version("0.21.6", "foo path").is_ok()); + assert!(check_version("0.21.7", "foo path").is_ok()); // Next major release is ERR - assert!(check_version("1.20.7", "foo path").is_err()); + assert!(check_version("1.20.8", "foo path").is_err()); // Next minor release is ERR - assert!(check_version("0.22.7", "foo path").is_err()); + assert!(check_version("0.22.8", "foo path").is_err()); // Next bugfix release is ERR (Change when we get to v1) - assert!(check_version("0.21.8", "foo path").is_err()); + assert!(check_version("0.21.9", "foo path").is_err()); } } diff --git a/src/lua/util.rs b/src/lua/util.rs index da01f3c..6d761e3 100644 --- a/src/lua/util.rs +++ b/src/lua/util.rs @@ -160,7 +160,7 @@ pub fn is_dir<'a>(util: Table<'a>, lua: &Lua) -> Result> { /// ``` pub fn is_file<'a>(util: Table<'a>, lua: &Lua) -> Result> { let func = - lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_dir()))?; + lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_file()))?; util.set("is_file", func)?; Ok(util) } @@ -654,7 +654,7 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result> { /// Get a [Style][3] object for the given path based on the LS_COLORS /// environment variable. /// -/// Type: function( path:string ) -> [Style][3]|nil +/// Type: function( path:string ) -> [Style][3] /// /// Example: /// @@ -664,7 +664,10 @@ pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result> { /// ``` pub fn lscolor<'a>(util: Table<'a>, lua: &Lua) -> Result> { let func = lua.create_function(move |lua, path: String| { - let style = LS_COLORS.style_for_path(path).map(Style::from); + let style = LS_COLORS + .style_for_path(path) + .map(Style::from) + .unwrap_or_default(); lua::serialize(lua, &style).map_err(LuaError::custom) })?; util.set("lscolor", func)?; diff --git a/src/runner.rs b/src/runner.rs index 4b80274..4470135 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -8,7 +8,8 @@ use crate::explorer; use crate::lua; use crate::pipe; use crate::pwd_watcher; -use crate::ui; +use crate::ui::NO_COLOR; +use crate::ui::UI; use crate::yaml; use anyhow::{bail, Error, Result}; use crossterm::event; @@ -285,7 +286,7 @@ impl Runner { tx_pwd_watcher.send(app.pwd.clone())?; let mut result = Ok(None); - let session_path = app.session_path.to_owned(); + let session_path = app.session_path.clone(); term::enable_raw_mode()?; @@ -341,6 +342,9 @@ impl Runner { None, ))?; + // UI + let mut ui = UI::new(&lua); + 'outer: for task in rx_msg_in { match app.handle_task(task) { Ok(a) => { @@ -411,7 +415,7 @@ impl Runner { } ScrollUpHalf => { - app = app.focus_next_by_relative_index( + app = app.focus_previous_by_relative_index( terminal.size()?.height as usize / 2, )?; } @@ -470,13 +474,13 @@ impl Runner { } if app.pwd != last_pwd { - last_pwd = app.pwd.clone(); + last_pwd.clone_from(&app.pwd); // $PWD watcher tx_pwd_watcher.send(app.pwd.clone())?; // OSC 7: Change CWD - if !(*ui::NO_COLOR) { + if !(*NO_COLOR) { write!( terminal.backend_mut(), "\x1b]7;file://{}{}\x1b\\", @@ -493,7 +497,7 @@ impl Runner { } // UI - terminal.draw(|f| ui::draw(f, &app, &lua))?; + terminal.draw(|f| ui.draw(f, &app))?; } EnableMouse => { diff --git a/src/ui.rs b/src/ui.rs index 0d3dda0..5ff5d99 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -60,6 +60,33 @@ pub fn string_to_text<'a>(string: String) -> Text<'a> { } } +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Rect { + x: u16, + y: u16, + height: u16, + width: u16, +} + +impl From for Rect { + fn from(tui: TuiRect) -> Self { + Self { + x: tui.x, + y: tui.y, + height: tui.height, + width: tui.width, + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct ContentRendererArg { + pub app: app::LuaContextLight, + pub screen_size: Rect, + pub layout_size: Rect, + pub scrolltop: u16, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct LayoutOptions { @@ -81,7 +108,7 @@ impl LayoutOptions { self.margin = other.margin.or(self.margin); self.horizontal_margin = other.horizontal_margin.or(self.horizontal_margin); self.vertical_margin = other.vertical_margin.or(self.vertical_margin); - self.constraints = other.constraints.to_owned().or(self.constraints); + self.constraints = other.constraints.clone().or(self.constraints); self } } @@ -154,7 +181,7 @@ impl Layout { }, ) => Self::Horizontal { config: sconfig.extend(oconfig), - splits: osplits.to_owned(), + splits: osplits.clone(), }, ( @@ -168,9 +195,9 @@ impl Layout { }, ) => Self::Vertical { config: sconfig.extend(oconfig), - splits: osplits.to_owned(), + splits: osplits.clone(), }, - (_, other) => other.to_owned(), + (_, other) => other.clone(), } } @@ -192,7 +219,7 @@ impl Layout { }, other => { if other == *target { - replacement.to_owned() + replacement.clone() } else { other } @@ -364,14 +391,10 @@ impl Style { pub fn extend(mut self, other: &Self) -> Self { self.fg = other.fg.or(self.fg); self.bg = other.bg.or(self.bg); - self.add_modifiers = extend_optional_modifiers( - self.add_modifiers, - other.add_modifiers.to_owned(), - ); - self.sub_modifiers = extend_optional_modifiers( - self.sub_modifiers, - other.sub_modifiers.to_owned(), - ); + self.add_modifiers = + extend_optional_modifiers(self.add_modifiers, other.add_modifiers.clone()); + self.sub_modifiers = + extend_optional_modifiers(self.sub_modifiers, other.sub_modifiers.clone()); self } } @@ -594,12 +617,12 @@ pub struct ResolvedNodeUiMetadata { impl From for ResolvedNodeUiMetadata { fn from(node: ResolvedNode) -> Self { Self { - absolute_path: node.absolute_path.to_owned(), - extension: node.extension.to_owned(), + absolute_path: node.absolute_path.clone(), + extension: node.extension.clone(), is_dir: node.is_dir, is_file: node.is_file, is_readonly: node.is_readonly, - mime_essence: node.mime_essence.to_owned(), + mime_essence: node.mime_essence.clone(), size: node.size, human_size: node.human_size, created: node.created, @@ -663,21 +686,21 @@ impl NodeUiMetadata { style: Style, ) -> Self { Self { - parent: node.parent.to_owned(), - relative_path: node.relative_path.to_owned(), - absolute_path: node.absolute_path.to_owned(), - extension: node.extension.to_owned(), + parent: node.parent.clone(), + relative_path: node.relative_path.clone(), + absolute_path: node.absolute_path.clone(), + extension: node.extension.clone(), is_symlink: node.is_symlink, is_broken: node.is_broken, is_dir: node.is_dir, is_file: node.is_file, is_readonly: node.is_readonly, - mime_essence: node.mime_essence.to_owned(), + mime_essence: node.mime_essence.clone(), size: node.size, - human_size: node.human_size.to_owned(), - permissions: node.permissions.to_owned(), - canonical: node.canonical.to_owned().map(ResolvedNode::into), - symlink: node.symlink.to_owned().map(ResolvedNode::into), + human_size: node.human_size.clone(), + permissions: node.permissions, + canonical: node.canonical.clone().map(ResolvedNode::into), + symlink: node.symlink.clone().map(ResolvedNode::into), created: node.created, last_modified: node.last_modified, uid: node.uid, @@ -703,7 +726,7 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> { .borders(TuiBorders::from_bits_truncate( config .borders - .to_owned() + .clone() .unwrap_or_default() .iter() .map(|b| b.bits()) @@ -718,788 +741,750 @@ pub fn block<'a>(config: PanelUiConfig, default_title: String) -> Block<'a> { .border_style(config.border_style) } -fn draw_table( - f: &mut Frame, - screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - lua: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config.default.to_owned().extend(&panel_config.table); - let app_config = app.config.to_owned(); - let header_height = app_config.general.table.header.height.unwrap_or(1); - let height: usize = - (layout_size.height.max(header_height + 2) - (header_height + 2)).into(); - let row_style = app_config.general.table.row.style.to_owned(); - - let rows = app - .directory_buffer - .as_ref() - .map(|dir| { - dir.nodes - .iter() - .enumerate() - .skip(height * (dir.focus / height.max(1))) - .take(height) - .map(|(index, node)| { - let is_focused = dir.focus == index; - - let is_selected = app - .selection - .iter() - .any(|s| s.absolute_path == node.absolute_path); - - let is_first = index == 0; - let is_last = index == dir.total.max(1) - 1; - - let tree = app_config - .general - .table - .tree - .to_owned() - .map(|t| { - if is_last { - t.2.format - } else if is_first { - t.0.format - } else { - t.1.format - } - }) - .unwrap_or_default(); +pub struct UI<'lua> { + pub lua: &'lua Lua, + pub screen_size: TuiRect, + pub scrolltop: usize, +} - let node_type = app_config.node_types.get(node); +impl<'lua> UI<'lua> { + pub fn new(lua: &'lua Lua) -> Self { + let screen_size = Default::default(); + let scrolltop = 0; + Self { + lua, + scrolltop, + screen_size, + } + } +} - let (relative_index, is_before_focus, is_after_focus) = - match dir.focus.cmp(&index) { - Ordering::Greater => (dir.focus - index, true, false), - Ordering::Less => (index - dir.focus, false, true), - Ordering::Equal => (0, false, false), - }; +impl UI<'_> { + fn draw_table(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; + let config = panel_config.default.clone().extend(&panel_config.table); + let app_config = app.config.clone(); + let header_height = app_config.general.table.header.height.unwrap_or(1); + let height: usize = layout_size.height.saturating_sub(header_height + 2).into(); + let row_style = app_config.general.table.row.style.clone(); + + let rows = app + .directory_buffer + .as_ref() + .map(|dir| { + // Scroll + if app.config.general.paginated_scrolling { + // Paginated scrolling + self.scrolltop = height * (dir.focus / height.max(1)) + } else { + // Vim-like-scrolling + let padding = app.config.general.scroll_padding; + if dir.focus >= (self.scrolltop + height).saturating_sub(padding) { + // Scrolling down + self.scrolltop = (dir.focus + padding + 1) + .saturating_sub(height) + .min(dir.total.saturating_sub(height)); + } else if dir.focus < self.scrolltop + padding { + // Scrolling up + self.scrolltop = dir.focus.saturating_sub(padding); + } + }; - let (mut prefix, mut suffix, mut style) = { - let ui = app_config.general.default_ui.to_owned(); - (ui.prefix, ui.suffix, ui.style.extend(&node_type.style)) - }; + dir.nodes + .iter() + .enumerate() + .skip(self.scrolltop) + .take(height) + .map(|(index, node)| { + let is_focused = dir.focus == index; + + let is_selected = app + .selection + .iter() + .any(|s| s.absolute_path == node.absolute_path); + + let is_first = index == 0; + let is_last = index == dir.total.max(1) - 1; + + let tree = app_config + .general + .table + .tree + .clone() + .map(|t| { + if is_last { + t.2.format + } else if is_first { + t.0.format + } else { + t.1.format + } + }) + .unwrap_or_default(); + + let node_type = app_config.node_types.get(node); + + let (relative_index, is_before_focus, is_after_focus) = + match dir.focus.cmp(&index) { + Ordering::Greater => (dir.focus - index, true, false), + Ordering::Less => (index - dir.focus, false, true), + Ordering::Equal => (0, false, false), + }; + + let (mut prefix, mut suffix, mut style) = { + let ui = app_config.general.default_ui.clone(); + (ui.prefix, ui.suffix, ui.style.extend(&node_type.style)) + }; - if is_focused && is_selected { - let ui = app_config.general.focus_selection_ui.to_owned(); - prefix = ui.prefix.to_owned().or(prefix); - suffix = ui.suffix.to_owned().or(suffix); - style = style.extend(&ui.style); - } else if is_selected { - let ui = app_config.general.selection_ui.to_owned(); - prefix = ui.prefix.to_owned().or(prefix); - suffix = ui.suffix.to_owned().or(suffix); - style = style.extend(&ui.style); - } else if is_focused { - let ui = app_config.general.focus_ui.to_owned(); - prefix = ui.prefix.to_owned().or(prefix); - suffix = ui.suffix.to_owned().or(suffix); - style = style.extend(&ui.style); - }; + if is_focused && is_selected { + let ui = app_config.general.focus_selection_ui.clone(); + prefix = ui.prefix.clone().or(prefix); + suffix = ui.suffix.clone().or(suffix); + style = style.extend(&ui.style); + } else if is_selected { + let ui = app_config.general.selection_ui.clone(); + prefix = ui.prefix.clone().or(prefix); + suffix = ui.suffix.clone().or(suffix); + style = style.extend(&ui.style); + } else if is_focused { + let ui = app_config.general.focus_ui.clone(); + prefix = ui.prefix.clone().or(prefix); + suffix = ui.suffix.clone().or(suffix); + style = style.extend(&ui.style); + }; - let meta = NodeUiMetadata::new( - node, - index, - relative_index, - is_before_focus, - is_after_focus, - tree.unwrap_or_default(), - prefix.unwrap_or_default(), - suffix.unwrap_or_default(), - is_selected, - is_focused, - dir.total, - node_type.meta, - style, - ); - - let cols = lua::serialize::(lua, &meta) - .map(|v| { - app_config - .general - .table - .row - .cols - .to_owned() - .unwrap_or_default() - .iter() - .filter_map(|c| { - c.format.as_ref().map(|f| { - let out = lua::call(lua, f, v.clone()) - .unwrap_or_else(|e| format!("{e:?}")); - (string_to_text(out), c.style.to_owned()) + let meta = NodeUiMetadata::new( + node, + index, + relative_index, + is_before_focus, + is_after_focus, + tree.unwrap_or_default(), + prefix.unwrap_or_default(), + suffix.unwrap_or_default(), + is_selected, + is_focused, + dir.total, + node_type.meta, + style, + ); + + let cols = lua::serialize::(self.lua, &meta) + .map(|v| { + app_config + .general + .table + .row + .cols + .clone() + .unwrap_or_default() + .iter() + .filter_map(|c| { + c.format.as_ref().map(|f| { + let out = lua::call(self.lua, f, v.clone()) + .unwrap_or_else(|e| format!("{e:?}")); + (string_to_text(out), c.style.clone()) + }) }) - }) - .collect::>() - }) - .unwrap_or_default() - .into_iter() - .map(|(text, style)| Cell::from(text).style(style)) - .collect::>(); + .collect::>() + }) + .unwrap_or_default() + .into_iter() + .map(|(text, style)| Cell::from(text).style(style)) + .collect::>(); + + Row::new(cols).style(row_style.clone()) + }) + .collect::>() + }) + .unwrap_or_default(); + + let table_constraints: Vec = app_config + .general + .table + .col_widths + .clone() + .unwrap_or_default() + .into_iter() + .map(|c| c.to_tui(self.screen_size, layout_size)) + .collect(); + + let pwd = if let Some(vroot) = app.vroot.as_ref() { + app.pwd.strip_prefix(vroot).unwrap_or(&app.pwd) + } else { + &app.pwd + } + .trim_matches('/'); - Row::new(cols).style(row_style.to_owned()) - }) - .collect::>() - }) - .unwrap_or_default(); - - let table_constraints: Vec = app_config - .general - .table - .col_widths - .to_owned() - .unwrap_or_default() - .into_iter() - .map(|c| c.to_tui(screen_size, layout_size)) - .collect(); - - let pwd = if let Some(vroot) = app.vroot.as_ref() { - app.pwd.strip_prefix(vroot).unwrap_or(&app.pwd) - } else { - &app.pwd + let pwd = path::escape(pwd); + + let vroot_indicator = if app.vroot.is_some() { "vroot:" } else { "" }; + + let node_count = app.directory_buffer.as_ref().map(|d| d.total).unwrap_or(0); + let node_count = if node_count == 0 { + String::new() + } else { + format!("({node_count}) ") + }; + + let table = Table::new(rows, table_constraints) + .style(app_config.general.table.style.clone()) + .highlight_style(app_config.general.focus_ui.style.clone()) + .column_spacing(app_config.general.table.col_spacing.unwrap_or_default()) + .block(block( + config, + format!(" {vroot_indicator}/{pwd} {node_count}"), + )); + + let table = table.clone().header( + Row::new( + app_config + .general + .table + .header + .cols + .clone() + .unwrap_or_default() + .iter() + .map(|c| { + Cell::from(c.format.clone().unwrap_or_default()) + .style(c.style.clone()) + }) + .collect::>(), + ) + .height(header_height) + .style(app_config.general.table.header.style.clone()), + ); + + f.render_widget(table, layout_size); } - .trim_matches('/'); - let pwd = path::escape(pwd); + fn draw_selection(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; + let config = panel_config.default.clone().extend(&panel_config.selection); - let vroot_indicator = if app.vroot.is_some() { "vroot:" } else { "" }; + let selection_count = app.selection.len(); - let node_count = app.directory_buffer.as_ref().map(|d| d.total).unwrap_or(0); - let node_count = if node_count == 0 { - String::new() - } else { - format!("({node_count}) ") - }; - - let table = Table::new(rows, table_constraints) - .style(app_config.general.table.style.to_owned()) - .highlight_style(app_config.general.focus_ui.style.to_owned()) - .column_spacing(app_config.general.table.col_spacing.unwrap_or_default()) - .block(block( - config, - format!(" {vroot_indicator}/{pwd} {node_count}"), - )); + let selection: Vec = app + .selection + .iter() + .rev() + .take((layout_size.height.max(2) - 2).into()) + .rev() + .map(|n| { + let out = app + .config + .general + .selection + .item + .format + .as_ref() + .map(|f| { + lua::serialize::(self.lua, n) + .and_then(|n| lua::call(self.lua, f, n)) + .unwrap_or_else(|e| format!("{e:?}")) + }) + .unwrap_or_else(|| n.absolute_path.clone()); + string_to_text(out) + }) + .map(|i| { + ListItem::new(i).style(app.config.general.selection.item.style.clone()) + }) + .collect(); - let table = table.to_owned().header( - Row::new( - app_config - .general - .table - .header - .cols - .to_owned() - .unwrap_or_default() - .iter() - .map(|c| { - Cell::from(c.format.to_owned().unwrap_or_default()) - .style(c.style.to_owned()) - }) - .collect::>(), - ) - .height(header_height) - .style(app_config.general.table.header.style.to_owned()), - ); + // Selected items + let selection_count = if selection_count == 0 { + String::new() + } else { + format!("({selection_count}) ") + }; - f.render_widget(table, layout_size); -} + let selection_list = List::new(selection) + .block(block(config, format!(" Selection {selection_count}"))); + + f.render_widget(selection_list, layout_size); + } + + fn draw_help_menu(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; -fn draw_selection( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - lua: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config - .default - .to_owned() - .extend(&panel_config.selection); - - let selection_count = app.selection.len(); - - let selection: Vec = app - .selection - .iter() - .rev() - .take((layout_size.height.max(2) - 2).into()) - .rev() - .map(|n| { - let out = app - .config - .general - .selection - .item - .format + let config = panel_config.default.clone().extend(&panel_config.help_menu); + + let help_menu_rows = app + .mode + .help_menu() + .into_iter() + .map(|l| match l { + HelpMenuLine::Paragraph(p) => Row::new([Cell::from(p)].to_vec()), + HelpMenuLine::KeyMap(k, remaps, h) => Row::new({ + if app.config.general.hide_remaps_in_help_menu { + [Cell::from(k), Cell::from(h)].to_vec() + } else { + [Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)] + .to_vec() + } + }), + }) + .collect::>(); + + let widths = if app.config.general.hide_remaps_in_help_menu { + vec![TuiConstraint::Percentage(20), TuiConstraint::Percentage(80)] + } else { + vec![ + TuiConstraint::Percentage(20), + TuiConstraint::Percentage(20), + TuiConstraint::Percentage(60), + ] + }; + let help_menu = Table::new(help_menu_rows, widths).block(block( + config, + format!(" Help [{}{}] ", &app.mode.name, read_only_indicator(app)), + )); + f.render_widget(help_menu, layout_size); + } + + fn draw_input_buffer( + &mut self, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + ) { + if let Some(input) = app.input.buffer.as_ref() { + let panel_config = &app.config.general.panel_ui; + let config = panel_config + .default + .clone() + .extend(&panel_config.input_and_logs); + + let cursor_offset_left = config + .borders .as_ref() - .map(|f| { - lua::serialize::(lua, n) - .and_then(|n| lua::call(lua, f, n)) - .unwrap_or_else(|e| format!("{e:?}")) - }) - .unwrap_or_else(|| n.absolute_path.clone()); - string_to_text(out) - }) - .map(|i| { - ListItem::new(i).style(app.config.general.selection.item.style.to_owned()) - }) - .collect(); - - // Selected items - let selection_count = if selection_count == 0 { - String::new() - } else { - format!("({selection_count}) ") - }; + .map(|b| b.contains(&Border::Left)) + .unwrap_or(false) as u16 + + app.input.prompt.chars().count() as u16; - let selection_list = List::new(selection) - .block(block(config, format!(" Selection {selection_count}"))); + let cursor_offset_right = config + .borders + .as_ref() + .map(|b| b.contains(&Border::Right)) + .unwrap_or(false) as u16 + + 1; + + let offset_width = cursor_offset_left + cursor_offset_right; + let width = layout_size.width.max(offset_width) - offset_width; + let scroll = input.visual_scroll(width.into()) as u16; + + let input_buf = Paragraph::new(Line::from(vec![ + Span::styled( + app.input.prompt.clone(), + app.config.general.prompt.style.clone(), + ), + Span::raw(input.value()), + ])) + .scroll((0, scroll)) + .block(block( + config, + format!( + " Input [{}{}]{} ", + app.mode.name, + read_only_indicator(app), + selection_indicator(app), + ), + )); + + f.render_widget(input_buf, layout_size); + f.set_cursor( + // Put cursor past the end of the input text + layout_size.x + + (input.visual_cursor() as u16).min(width) + + cursor_offset_left, + // Move one line down, from the border to the input line + layout_size.y + 1, + ); + }; + } - f.render_widget(selection_list, layout_size); -} + fn draw_sort_n_filter( + &mut self, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + ) { + let panel_config = &app.config.general.panel_ui; + let config = panel_config + .default + .clone() + .extend(&panel_config.sort_and_filter); + let ui = app.config.general.sort_and_filter_ui.clone(); + let filter_by: &IndexSet = &app.explorer_config.filters; + let sort_by: &IndexSet = &app.explorer_config.sorters; + let search = app.explorer_config.searcher.as_ref(); + + let defaultui = &ui.default_identifier; + let forwardui = defaultui + .clone() + .extend(&ui.sort_direction_identifiers.forward); + let reverseui = defaultui + .clone() + .extend(&ui.sort_direction_identifiers.reverse); + + let orderedui = defaultui + .clone() + .extend(&ui.search_direction_identifiers.ordered); + let unorderedui = defaultui + .clone() + .extend(&ui.search_direction_identifiers.unordered); + + let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false); + + let mut spans = filter_by + .iter() + .map(|f| { + ui.filter_identifiers + .get(&f.filter) + .map(|u| { + let ui = defaultui.clone().extend(u); + ( + Span::styled( + ui.format.clone().unwrap_or_default(), + ui.style.clone(), + ), + Span::styled(f.input.clone(), ui.style), + ) + }) + .unwrap_or((Span::raw("f"), Span::raw(""))) + }) + .chain(search.iter().map(|s| { + ui.search_identifiers + .get(&s.algorithm) + .map(|u| { + let direction = if s.unordered { + &unorderedui + } else { + &orderedui + }; + let ui = defaultui.clone().extend(u); + let f = ui + .format + .as_ref() + .map(|f| format!("{f}{p}", p = &s.pattern)) + .unwrap_or_else(|| s.pattern.clone()); + ( + Span::styled(f, ui.style), + Span::styled( + direction.format.clone().unwrap_or_default(), + direction.style.clone(), + ), + ) + }) + .unwrap_or((Span::raw("/"), Span::raw(&s.pattern))) + })) + .chain( + sort_by + .iter() + .map(|s| { + let direction = if s.reverse { &reverseui } else { &forwardui }; + ui.sorter_identifiers + .get(&s.sorter) + .map(|u| { + let ui = defaultui.clone().extend(u); + ( + Span::styled( + ui.format.clone().unwrap_or_default(), + ui.style, + ), + Span::styled( + direction.format.clone().unwrap_or_default(), + direction.style.clone(), + ), + ) + }) + .unwrap_or((Span::raw("s"), Span::raw(""))) + }) + .take(if !is_ordered_search { sort_by.len() } else { 0 }), + ) + .zip(std::iter::repeat(Span::styled( + ui.separator.format.clone().unwrap_or_default(), + ui.separator.style.clone(), + ))) + .flat_map(|((a, b), c)| vec![a, b, c]) + .collect::>(); + + spans.pop(); + + let item_count = filter_by.len() + sort_by.len(); + let item_count = if item_count == 0 { + String::new() + } else { + format!("({item_count}) ") + }; -fn draw_help_menu( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - - let config = panel_config - .default - .to_owned() - .extend(&panel_config.help_menu); - - let help_menu_rows = app - .mode - .help_menu() - .into_iter() - .map(|l| match l { - HelpMenuLine::Paragraph(p) => Row::new([Cell::from(p)].to_vec()), - HelpMenuLine::KeyMap(k, remaps, h) => Row::new({ - if app.config.general.hide_remaps_in_help_menu { - [Cell::from(k), Cell::from(h)].to_vec() - } else { - [Cell::from(k), Cell::from(remaps.join("|")), Cell::from(h)].to_vec() - } - }), - }) - .collect::>(); + let p = Paragraph::new(Line::from(spans)) + .block(block(config, format!(" Sort & filter {item_count}"))); - let widths = if app.config.general.hide_remaps_in_help_menu { - vec![TuiConstraint::Percentage(20), TuiConstraint::Percentage(80)] - } else { - vec![ - TuiConstraint::Percentage(20), - TuiConstraint::Percentage(20), - TuiConstraint::Percentage(60), - ] - }; - let help_menu = Table::new(help_menu_rows, widths).block(block( - config, - format!(" Help [{}{}] ", &app.mode.name, read_only_indicator(app)), - )); - f.render_widget(help_menu, layout_size); -} + f.render_widget(p, layout_size); + } -fn draw_input_buffer( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _: &Lua, -) { - if let Some(input) = app.input.buffer.as_ref() { + fn draw_logs(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { let panel_config = &app.config.general.panel_ui; let config = panel_config .default - .to_owned() + .clone() .extend(&panel_config.input_and_logs); + let logs_config = app.config.general.logs.clone(); + let logs = if app.logs_hidden { + vec![] + } else { + app.logs + .iter() + .rev() + .take(layout_size.height as usize) + .map(|log| { + let fd = format_description!("[hour]:[minute]:[second]"); + let time = + log.created_at.format(fd).unwrap_or_else(|_| "when?".into()); + let cfg = match log.level { + app::LogLevel::Info => &logs_config.info, + app::LogLevel::Warning => &logs_config.warning, + app::LogLevel::Success => &logs_config.success, + app::LogLevel::Error => &logs_config.error, + }; - let cursor_offset_left = config - .borders - .as_ref() - .map(|b| b.contains(&Border::Left)) - .unwrap_or(false) as u16 - + app.input.prompt.chars().count() as u16; + let prefix = + format!("{time}|{0}", cfg.format.clone().unwrap_or_default()); - let cursor_offset_right = config - .borders - .as_ref() - .map(|b| b.contains(&Border::Right)) - .unwrap_or(false) as u16 - + 1; - - let offset_width = cursor_offset_left + cursor_offset_right; - let width = layout_size.width.max(offset_width) - offset_width; - let scroll = input.visual_scroll(width.into()) as u16; - - let input_buf = Paragraph::new(Line::from(vec![ - Span::styled( - app.input.prompt.to_owned(), - app.config.general.prompt.style.to_owned(), - ), - Span::raw(input.value()), - ])) - .scroll((0, scroll)) - .block(block( + let padding = " ".repeat(prefix.chars().count()); + + let txt = log + .message + .lines() + .enumerate() + .map(|(i, line)| { + if i == 0 { + format!("{prefix}) {line}") + } else { + format!("{padding} {line}") + } + }) + .take(layout_size.height as usize) + .collect::>() + .join("\n"); + + ListItem::new(txt).style(cfg.style.clone()) + }) + .collect::>() + }; + + let logs_count = app.logs.len(); + let logs_count = if logs_count == 0 { + String::new() + } else { + format!(" ({logs_count})") + }; + + let logs_list = List::new(logs).block(block( config, format!( - " Input [{}{}]{} ", + " Logs{} [{}{}]{} ", + logs_count, app.mode.name, read_only_indicator(app), - selection_indicator(app), + selection_indicator(app) ), )); - f.render_widget(input_buf, layout_size); - f.set_cursor( - // Put cursor past the end of the input text - layout_size.x - + (input.visual_cursor() as u16).min(width) - + cursor_offset_left, - // Move one line down, from the border to the input line - layout_size.y + 1, - ); - }; -} - -fn draw_sort_n_filter( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config - .default - .to_owned() - .extend(&panel_config.sort_and_filter); - let ui = app.config.general.sort_and_filter_ui.to_owned(); - let filter_by: &IndexSet = &app.explorer_config.filters; - let sort_by: &IndexSet = &app.explorer_config.sorters; - let search = app.explorer_config.searcher.as_ref(); - - let defaultui = &ui.default_identifier; - let forwardui = defaultui - .to_owned() - .extend(&ui.sort_direction_identifiers.forward); - let reverseui = defaultui - .to_owned() - .extend(&ui.sort_direction_identifiers.reverse); - - let orderedui = defaultui - .to_owned() - .extend(&ui.search_direction_identifiers.ordered); - let unorderedui = defaultui - .to_owned() - .extend(&ui.search_direction_identifiers.unordered); - - let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false); - - let mut spans = filter_by - .iter() - .map(|f| { - ui.filter_identifiers - .get(&f.filter) - .map(|u| { - let ui = defaultui.to_owned().extend(u); - ( - Span::styled( - ui.format.to_owned().unwrap_or_default(), - ui.style.to_owned(), - ), - Span::styled(f.input.to_owned(), ui.style), - ) - }) - .unwrap_or((Span::raw("f"), Span::raw(""))) - }) - .chain(search.iter().map(|s| { - ui.search_identifiers - .get(&s.algorithm) - .map(|u| { - let direction = if s.unordered { - &unorderedui - } else { - &orderedui - }; - let ui = defaultui.to_owned().extend(u); - let f = ui - .format - .as_ref() - .map(|f| format!("{f}{p}", p = &s.pattern)) - .unwrap_or_else(|| s.pattern.clone()); - ( - Span::styled(f, ui.style), - Span::styled( - direction.format.to_owned().unwrap_or_default(), - direction.style.to_owned(), - ), - ) - }) - .unwrap_or((Span::raw("/"), Span::raw(&s.pattern))) - })) - .chain( - sort_by - .iter() - .map(|s| { - let direction = if s.reverse { &reverseui } else { &forwardui }; - ui.sorter_identifiers - .get(&s.sorter) - .map(|u| { - let ui = defaultui.to_owned().extend(u); - ( - Span::styled( - ui.format.to_owned().unwrap_or_default(), - ui.style, - ), - Span::styled( - direction.format.to_owned().unwrap_or_default(), - direction.style.to_owned(), - ), - ) - }) - .unwrap_or((Span::raw("s"), Span::raw(""))) - }) - .take(if !is_ordered_search { sort_by.len() } else { 0 }), - ) - .zip(std::iter::repeat(Span::styled( - ui.separator.format.to_owned().unwrap_or_default(), - ui.separator.style.to_owned(), - ))) - .flat_map(|((a, b), c)| vec![a, b, c]) - .collect::>(); - - spans.pop(); - - let item_count = filter_by.len() + sort_by.len(); - let item_count = if item_count == 0 { - String::new() - } else { - format!("({item_count}) ") - }; - - let p = Paragraph::new(Line::from(spans)) - .block(block(config, format!(" Sort & filter {item_count}"))); - - f.render_widget(p, layout_size); -} + f.render_widget(logs_list, layout_size); + } -fn draw_logs( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config - .default - .to_owned() - .extend(&panel_config.input_and_logs); - let logs_config = app.config.general.logs.to_owned(); - let logs = if app.logs_hidden { - vec![] - } else { - app.logs - .iter() - .rev() - .take(layout_size.height as usize) - .map(|log| { - let fd = format_description!("[hour]:[minute]:[second]"); - let time = log.created_at.format(fd).unwrap_or_else(|_| "when?".into()); - let cfg = match log.level { - app::LogLevel::Info => &logs_config.info, - app::LogLevel::Warning => &logs_config.warning, - app::LogLevel::Success => &logs_config.success, - app::LogLevel::Error => &logs_config.error, - }; + fn draw_nothing(&mut self, f: &mut Frame, layout_size: TuiRect, app: &app::App) { + let panel_config = &app.config.general.panel_ui; + let config = panel_config.default.clone(); + let nothing = Paragraph::new("").block(block(config, "".into())); + f.render_widget(nothing, layout_size); + } - let prefix = - format!("{time}|{0}", cfg.format.to_owned().unwrap_or_default()); + fn draw_dynamic( + &mut self, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + func: &str, + ) { + let ctx = ContentRendererArg { + app: app.to_lua_ctx_light(), + layout_size: layout_size.into(), + screen_size: self.screen_size.into(), + scrolltop: self.scrolltop as u16, + }; - let padding = " ".repeat(prefix.chars().count()); + let panel: CustomPanel = lua::serialize(self.lua, &ctx) + .and_then(|arg| lua::call(self.lua, func, arg)) + .unwrap_or_else(|e| CustomPanel::CustomParagraph { + ui: app.config.general.panel_ui.default.clone(), + body: format!("{e:?}"), + }); - let txt = log - .message - .lines() - .enumerate() - .map(|(i, line)| { - if i == 0 { - format!("{prefix}) {line}") - } else { - format!("{padding} {line}") - } - }) - .take(layout_size.height as usize) - .collect::>() - .join("\n"); + self.draw_static(f, layout_size, app, panel); + } - ListItem::new(txt).style(cfg.style.to_owned()) - }) - .collect::>() - }; + fn draw_static( + &mut self, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + panel: CustomPanel, + ) { + let defaultui = app.config.general.panel_ui.default.clone(); + match panel { + CustomPanel::CustomParagraph { ui, body } => { + let config = defaultui.extend(&ui); + let body = string_to_text(body); + let content = Paragraph::new(body).block(block(config, "".into())); + f.render_widget(content, layout_size); + } - let logs_count = app.logs.len(); - let logs_count = if logs_count == 0 { - String::new() - } else { - format!(" ({logs_count})") - }; - - let logs_list = List::new(logs).block(block( - config, - format!( - " Logs{} [{}{}]{} ", - logs_count, - app.mode.name, - read_only_indicator(app), - selection_indicator(app) - ), - )); - - f.render_widget(logs_list, layout_size); -} + CustomPanel::CustomList { ui, body } => { + let config = defaultui.extend(&ui); -pub fn draw_nothing( - f: &mut Frame, - _screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - _lua: &Lua, -) { - let panel_config = &app.config.general.panel_ui; - let config = panel_config.default.to_owned(); - let nothing = Paragraph::new("").block(block(config, "".into())); - f.render_widget(nothing, layout_size); -} + let items = body + .into_iter() + .map(string_to_text) + .map(ListItem::new) + .collect::>(); -pub fn draw_dynamic( - f: &mut Frame, - screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - func: &str, - lua: &Lua, -) { - let ctx = ContentRendererArg { - app: app.to_lua_ctx_light(), - layout_size: layout_size.into(), - screen_size: screen_size.into(), - }; - - let panel: CustomPanel = lua::serialize(lua, &ctx) - .and_then(|arg| lua::call(lua, func, arg)) - .unwrap_or_else(|e| CustomPanel::CustomParagraph { - ui: app.config.general.panel_ui.default.clone(), - body: format!("{e:?}"), - }); + let content = List::new(items).block(block(config, "".into())); + f.render_widget(content, layout_size); + } - draw_static(f, screen_size, layout_size, app, panel, lua); -} + CustomPanel::CustomTable { + ui, + widths, + col_spacing, + body, + } => { + let config = defaultui.extend(&ui); + let rows = body + .into_iter() + .map(|cols| { + Row::new( + cols.into_iter() + .map(string_to_text) + .map(Cell::from) + .collect::>(), + ) + }) + .collect::>(); -pub fn draw_static( - f: &mut Frame, - screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - panel: CustomPanel, - _lua: &Lua, -) { - let defaultui = app.config.general.panel_ui.default.clone(); - match panel { - CustomPanel::CustomParagraph { ui, body } => { - let config = defaultui.extend(&ui); - let body = string_to_text(body); - let content = Paragraph::new(body).block(block(config, "".into())); - f.render_widget(content, layout_size); - } + let widths = widths + .into_iter() + .map(|w| w.to_tui(self.screen_size, layout_size)) + .collect::>(); - CustomPanel::CustomList { ui, body } => { - let config = defaultui.extend(&ui); + let content = Table::new(rows, widths) + .column_spacing(col_spacing.unwrap_or(1)) + .block(block(config, "".into())); - let items = body - .into_iter() - .map(string_to_text) - .map(ListItem::new) - .collect::>(); + f.render_widget(content, layout_size); + } - let content = List::new(items).block(block(config, "".into())); - f.render_widget(content, layout_size); + CustomPanel::CustomLayout(layout) => { + self.draw_layout(layout, f, layout_size, app); + } } + } - CustomPanel::CustomTable { - ui, - widths, - col_spacing, - body, - } => { - let config = defaultui.extend(&ui); - let rows = body - .into_iter() - .map(|cols| { - Row::new( - cols.into_iter() - .map(string_to_text) - .map(Cell::from) - .collect::>(), + fn draw_layout( + &mut self, + layout: Layout, + f: &mut Frame, + layout_size: TuiRect, + app: &app::App, + ) { + match layout { + Layout::Nothing => self.draw_nothing(f, layout_size, app), + Layout::Table => self.draw_table(f, layout_size, app), + Layout::SortAndFilter => self.draw_sort_n_filter(f, layout_size, app), + Layout::HelpMenu => self.draw_help_menu(f, layout_size, app), + Layout::Selection => self.draw_selection(f, layout_size, app), + Layout::InputAndLogs => { + if app.input.buffer.is_some() { + self.draw_input_buffer(f, layout_size, app); + } else { + self.draw_logs(f, layout_size, app); + }; + } + Layout::Static(panel) => self.draw_static(f, layout_size, app, *panel), + Layout::Dynamic(ref func) => self.draw_dynamic(f, layout_size, app, func), + Layout::CustomContent(content) => { + draw_custom_content(self, f, layout_size, app, *content) + } + Layout::Horizontal { config, splits } => { + let chunks = TuiLayout::default() + .direction(Direction::Horizontal) + .constraints( + config + .constraints + .clone() + .unwrap_or_default() + .iter() + .map(|c| c.to_tui(self.screen_size, layout_size)) + .collect::>(), ) - }) - .collect::>(); - - let widths = widths - .into_iter() - .map(|w| w.to_tui(screen_size, layout_size)) - .collect::>(); - - let content = Table::new(rows, widths) - .column_spacing(col_spacing.unwrap_or(1)) - .block(block(config, "".into())); - - f.render_widget(content, layout_size); - } + .horizontal_margin( + config + .horizontal_margin + .or(config.margin) + .unwrap_or_default(), + ) + .vertical_margin( + config.vertical_margin.or(config.margin).unwrap_or_default(), + ) + .split(layout_size); - CustomPanel::CustomLayout(layout) => { - draw_layout(layout, f, screen_size, layout_size, app, _lua); - } - } -} + splits + .into_iter() + .zip(chunks.iter()) + .for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app)); + } -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct Rect { - x: u16, - y: u16, - height: u16, - width: u16, -} + Layout::Vertical { config, splits } => { + let chunks = TuiLayout::default() + .direction(Direction::Vertical) + .constraints( + config + .constraints + .clone() + .unwrap_or_default() + .iter() + .map(|c| c.to_tui(self.screen_size, layout_size)) + .collect::>(), + ) + .horizontal_margin( + config + .horizontal_margin + .or(config.margin) + .unwrap_or_default(), + ) + .vertical_margin( + config.vertical_margin.or(config.margin).unwrap_or_default(), + ) + .split(layout_size); -impl From for Rect { - fn from(tui: TuiRect) -> Self { - Self { - x: tui.x, - y: tui.y, - height: tui.height, - width: tui.width, + splits + .into_iter() + .zip(chunks.iter()) + .for_each(|(split, chunk)| self.draw_layout(split, f, *chunk, app)); + } } } -} - -#[derive(Debug, Clone, Serialize)] -pub struct ContentRendererArg { - pub app: app::LuaContextLight, - pub screen_size: Rect, - pub layout_size: Rect, -} -pub fn draw_layout( - layout: Layout, - f: &mut Frame, - screen_size: TuiRect, - layout_size: TuiRect, - app: &app::App, - lua: &Lua, -) { - match layout { - Layout::Nothing => draw_nothing(f, screen_size, layout_size, app, lua), - Layout::Table => draw_table(f, screen_size, layout_size, app, lua), - Layout::SortAndFilter => { - draw_sort_n_filter(f, screen_size, layout_size, app, lua) - } - Layout::HelpMenu => draw_help_menu(f, screen_size, layout_size, app, lua), - Layout::Selection => draw_selection(f, screen_size, layout_size, app, lua), - Layout::InputAndLogs => { - if app.input.buffer.is_some() { - draw_input_buffer(f, screen_size, layout_size, app, lua); - } else { - draw_logs(f, screen_size, layout_size, app, lua); - }; - } - Layout::Static(panel) => { - draw_static(f, screen_size, layout_size, app, *panel, lua) - } - Layout::Dynamic(ref func) => { - draw_dynamic(f, screen_size, layout_size, app, func, lua) - } - Layout::CustomContent(content) => { - draw_custom_content(f, screen_size, layout_size, app, *content, lua) - } - Layout::Horizontal { config, splits } => { - let chunks = TuiLayout::default() - .direction(Direction::Horizontal) - .constraints( - config - .constraints - .to_owned() - .unwrap_or_default() - .iter() - .map(|c| c.to_tui(screen_size, layout_size)) - .collect::>(), - ) - .horizontal_margin( - config - .horizontal_margin - .or(config.margin) - .unwrap_or_default(), - ) - .vertical_margin( - config.vertical_margin.or(config.margin).unwrap_or_default(), - ) - .split(layout_size); - - splits - .into_iter() - .zip(chunks.iter()) - .for_each(|(split, chunk)| { - draw_layout(split, f, screen_size, *chunk, app, lua) - }); - } - - Layout::Vertical { config, splits } => { - let chunks = TuiLayout::default() - .direction(Direction::Vertical) - .constraints( - config - .constraints - .to_owned() - .unwrap_or_default() - .iter() - .map(|c| c.to_tui(screen_size, layout_size)) - .collect::>(), - ) - .horizontal_margin( - config - .horizontal_margin - .or(config.margin) - .unwrap_or_default(), - ) - .vertical_margin( - config.vertical_margin.or(config.margin).unwrap_or_default(), - ) - .split(layout_size); - - splits - .into_iter() - .zip(chunks.iter()) - .for_each(|(split, chunk)| { - draw_layout(split, f, screen_size, *chunk, app, lua) - }); - } + pub fn draw(&mut self, f: &mut Frame, app: &app::App) { + self.screen_size = f.size(); + let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).clone(); + self.draw_layout(layout, f, self.screen_size, app); } } -pub fn draw(f: &mut Frame, app: &app::App, lua: &Lua) { - let screen_size = f.size(); - let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).to_owned(); - - draw_layout(layout, f, screen_size, screen_size, app, lua); -} - #[cfg(test)] mod tests { use super::*; @@ -1534,7 +1519,7 @@ mod tests { }; assert_eq!( - a.to_owned().extend(&b), + a.clone().extend(&b), Style { fg: Some(Color::Red), bg: Some(Color::Blue), @@ -1554,7 +1539,7 @@ mod tests { ); assert_eq!( - a.to_owned().extend(&c), + a.clone().extend(&c), Style { fg: Some(Color::Cyan), bg: Some(Color::Magenta),