diff -ur a/Cargo.lock b/Cargo.lock --- a/Cargo.lock 2021-05-08 03:04:44.000000000 +0300 +++ b/Cargo.lock 2021-08-07 11:25:14.695474018 +0300 @@ -54,12 +54,24 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] name = "base64" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] +name = "beef" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409" + +[[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -86,6 +98,12 @@ ] [[package]] +name = "bumpalo" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" + +[[package]] name = "cassowary" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -118,7 +136,7 @@ "libc", "num-integer", "num-traits", - "time", + "time 0.1.43", "winapi", ] @@ -131,19 +149,110 @@ "ansi_term", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", ] [[package]] +name = "colors-transform" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9226dbc05df4fb986f48d730b001532580883c4c06c5d1c213f4b34c1c157178" + +[[package]] +name = "const_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" + +[[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] +name = "crossbeam" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel 0.5.1", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils 0.8.4", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.4", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils 0.8.4", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.4", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.4", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] name = "crossbeam-utils" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -155,6 +264,76 @@ ] [[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "defer-drop" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18ae055245e14ed411f56dddf2a78caae87c25d9d6a18fb61f398a596cad77b4" +dependencies = [ + "crossbeam-channel 0.4.4", + "once_cell", +] + +[[package]] +name = "derive_builder" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" +dependencies = [ + "darling", + "derive_builder_core", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "dirs" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -166,6 +345,16 @@ ] [[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + +[[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -176,6 +365,17 @@ ] [[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users 0.4.0", + "winapi", +] + +[[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -187,18 +387,58 @@ ] [[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] +name = "env_logger" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "filetime" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.8", + "winapi", +] + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] name = "getrandom" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -252,29 +492,51 @@ ] [[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] name = "joshuto" -version = "0.8.6" +version = "0.9.0" dependencies = [ "alphanumeric-sort", "chrono", + "colors-transform", "dirs-next", + "filetime", "globset", "lazy_static", "libc", "open", "phf", - "rand 0.8.3", + "rand", "rustyline", "serde", "serde_derive", "shell-words", "shellexpand", "signal-hook", + "skim", "structopt", "termion", "toml", "trash", "tui", + "unicode-segmentation", "unicode-width", "users", "whoami", @@ -303,12 +565,27 @@ ] [[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] name = "memchr" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] +name = "memoffset" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" +dependencies = [ + "autocfg", +] + +[[package]] name = "nix" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -322,6 +599,31 @@ ] [[package]] +name = "nix" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + +[[package]] name = "num-integer" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -341,12 +643,28 @@ ] [[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] name = "open" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -374,7 +692,7 @@ checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" dependencies = [ "phf_shared", - "rand 0.7.3", + "rand", ] [[package]] @@ -462,42 +780,20 @@ dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_chacha", + "rand_core", + "rand_hc", "rand_pcg", ] [[package]] -name = "rand" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" -dependencies = [ - "libc", - "rand_chacha 0.3.0", - "rand_core 0.6.2", - "rand_hc 0.3.0", -] - -[[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.2", + "rand_core", ] [[package]] @@ -510,39 +806,46 @@ ] [[package]] -name = "rand_core" -version = "0.6.2" +name = "rand_hc" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "getrandom 0.2.2", + "rand_core", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rand_pcg" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" dependencies = [ - "rand_core 0.5.1", + "rand_core", ] [[package]] -name = "rand_hc" -version = "0.3.0" +name = "rayon" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" dependencies = [ - "rand_core 0.6.2", + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", ] [[package]] -name = "rand_pcg" -version = "0.2.1" +name = "rayon-core" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ - "rand_core 0.5.1", + "crossbeam-channel 0.5.1", + "crossbeam-deque", + "crossbeam-utils 0.8.4", + "lazy_static", + "num_cpus", ] [[package]] @@ -616,7 +919,16 @@ "base64", "blake2b_simd", "constant_time_eq", - "crossbeam-utils", + "crossbeam-utils 0.8.4", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", ] [[package]] @@ -625,18 +937,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b" dependencies = [ - "dirs", + "dirs 1.0.5", "libc", "log", "memchr", - "nix", + "nix 0.13.1", "unicode-segmentation", "unicode-width", - "utf8parse", + "utf8parse 0.1.1", "winapi", ] [[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] name = "serde" version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -654,6 +993,23 @@ ] [[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] name = "shell-words" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -669,6 +1025,12 @@ ] [[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] name = "signal-hook" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -694,12 +1056,105 @@ checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27" [[package]] +name = "skim" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9d19f904221fab15163486d2ce116cb86e60296470bb4e956d6687f04ebbb4" +dependencies = [ + "atty", + "beef", + "bitflags", + "chrono", + "clap", + "crossbeam", + "defer-drop", + "derive_builder", + "env_logger", + "fuzzy-matcher", + "lazy_static", + "log", + "nix 0.19.1", + "rayon", + "regex", + "shlex", + "time 0.2.26", + "timer", + "tuikit", + "unicode-width", + "vte", +] + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + +[[package]] name = "structopt" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -735,6 +1190,25 @@ ] [[package]] +name = "term" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" +dependencies = [ + "dirs 2.0.2", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] name = "termion" version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -756,6 +1230,15 @@ ] [[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] name = "time" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -766,6 +1249,53 @@ ] [[package]] +name = "time" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a8cbfbf47955132d0202d1662f49b2423ae35862aee471f3ba4b133358f372" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "timer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d42176308937165701f50638db1c31586f183f1aab416268216577aec7306b" +dependencies = [ + "chrono", +] + +[[package]] name = "toml" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -797,6 +1327,20 @@ ] [[package]] +name = "tuikit" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c628cfc5752254a33ebccf73eb79ef6508fab77de5d5ef76246b5e45010a51f" +dependencies = [ + "bitflags", + "lazy_static", + "log", + "nix 0.14.1", + "term", + "unicode-width", +] + +[[package]] name = "unicode-segmentation" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -831,6 +1375,12 @@ checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" [[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + +[[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -849,6 +1399,27 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] +name = "vte" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7745610024d50ab1ebfa41f8f8ee361c567f7ab51032f93cc1cc4cbf0c547a" +dependencies = [ + "arrayvec", + "utf8parse 0.2.0", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -861,6 +1432,60 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] name = "which" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -893,6 +1518,15 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff -ur a/Cargo.toml b/Cargo.toml --- a/Cargo.toml 2021-05-08 03:04:44.000000000 +0300 +++ b/Cargo.toml 2021-08-07 11:25:14.695474018 +0300 @@ -10,7 +10,9 @@ [dependencies] alphanumeric-sort = "^1" chrono = "^0" +colors-transform = "^0" dirs-next = "^2" +filetime = "^0" globset = "^0" lazy_static = "^1" libc = "^0" @@ -22,6 +24,7 @@ shell-words = "^1" shellexpand = "^2" signal-hook = "^0" +skim = "^0" structopt = "^0" termion = "^1" toml = "^0" @@ -32,7 +35,7 @@ xdg = "^2" phf = { version = "^0", features = ["macros"], optional = true } trash = { version = "^1", optional = true } - +unicode-segmentation = "^1" [features] devicons = [ "phf" ] file_mimetype = [] diff -ur a/config/joshuto.toml b/config/joshuto.toml --- a/config/joshuto.toml 2021-05-08 03:04:44.000000000 +0300 +++ b/config/joshuto.toml 2021-08-07 11:25:14.695474018 +0300 @@ -4,9 +4,6 @@ xdg_open = false use_trash = true -# currently does not work -max_preview_size = 2097152 # 2MB - [display] # ratios for parent view, current view and preview collapse_preview = true @@ -23,3 +20,12 @@ case_sensitive = false directories_first = true reverse = false + +[preview] +max_preview_size = 2097152 # 2MB +preview_images = false +preview_script = "~/.config/joshuto/preview.sh" + +[tab] +# inherit, home, root +home_page = "home" diff -ur a/config/keymap.toml b/config/keymap.toml --- a/config/keymap.toml 2021-05-08 03:04:44.000000000 +0300 +++ b/config/keymap.toml 2021-08-07 11:25:14.695474018 +0300 @@ -36,13 +36,13 @@ [[mapcommand]] command = "cursor_move_up" -keys = [ "up" ] +keys = [ "arrow_up" ] [[mapcommand]] command = "cursor_move_up" keys = [ "k" ] [[mapcommand]] command = "cursor_move_down" -keys = [ "down" ] +keys = [ "arrow_down" ] [[mapcommand]] command = "cursor_move_down" keys = [ "j" ] @@ -73,16 +73,16 @@ keys = [ "]" ] [[mapcommand]] -command = "open_file" -keys = [ "right" ] +command = "open" +keys = [ "arrow_right" ] [[mapcommand]] -command = "open_file" +command = "open" keys = [ "l" ] [[mapcommand]] -command = "open_file" +command = "open" keys = [ "\n" ] [[mapcommand]] -command = "open_file_with" +command = "open_with" keys = [ "r" ] [[mapcommand]] @@ -90,7 +90,7 @@ keys = [ "c", "d" ] [[mapcommand]] command = "cd .." -keys = [ "left" ] +keys = [ "arrow_left" ] [[mapcommand]] command = "cd .." keys = [ "h" ] @@ -102,6 +102,15 @@ command = "copy_files" keys = [ "y", "y" ] [[mapcommand]] +command = "copy_filename" +keys = [ "y", "n" ] +[[mapcommand]] +command = "copy_filepath" +keys = [ "y", "p" ] +[[mapcommand]] +command = "copy_dirname" +keys = [ "y", "d" ] +[[mapcommand]] command = "paste_files" keys = [ "p", "p" ] [[mapcommand]] @@ -152,11 +161,17 @@ command = ":" keys = [ ";" ] [[mapcommand]] +command = ":" +keys = [ ":" ] +[[mapcommand]] command = ":mkdir " keys = [ "m", "k" ] [[mapcommand]] command = ":rename " keys = [ "c", "w" ] +[[mapcommand]] +command = ":touch" +keys = [ "f", "t" ] [[mapcommand]] command = "sort lexical" @@ -168,6 +183,9 @@ command = "sort natural" keys = [ "s", "n" ] [[mapcommand]] +command = "sort ext" +keys = [ "s", "e" ] +[[mapcommand]] command = "sort reverse" keys = [ "s", "r" ] diff -ur a/config/mimetype.toml b/config/mimetype.toml --- a/config/mimetype.toml 2021-05-08 03:04:44.000000000 +0300 +++ b/config/mimetype.toml 2021-08-07 11:25:14.695474018 +0300 @@ -1,314 +1,156 @@ -[extension] - -## image formats -bmp = [ - { command = "qimgv", fork = true, silent = true }, - { command = "krita", fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true } ] -gif = [ - { command = "qimgv", fork = true, silent = true }, - { command = "krita", fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true } ] -heic = [ - { command = "qimgv", fork = true, silent = true }, - { command = "krita", fork = true, silent = true }, +[class] +audio_default = [ + { command = "mpv", args = [ "--" ] }, { command = "mediainfo", confirm_exit = true } ] -jpeg = [ - { command = "qimgv", fork = true, silent = true }, - { command = "krita", fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -jpe = [ - { command = "qimgv", fork = true, silent = true }, - { command = "krita", fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -jpg = [ - { command = "qimgv", fork = true, silent = true }, - { command = "krita", fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -pgm = [ - { command = "qimgv", fork = true, silent = true }, - { command = "krita", fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -png = [ + +image_default = [ { command = "qimgv", fork = true, silent = true }, { command = "krita", fork = true, silent = true }, { command = "mediainfo", confirm_exit = true }, -ppm = [ - { command = "qimgv", fork = true, silent = true }, - { command = "krita", fork = true, silent = true }, + { command = "swappy", args = [ "-f" ], fork = true } ] + +video_default = [ + { command = "mpv", args = [ "--" ] , fork = true, silent = true }, { command = "mediainfo", confirm_exit = true }, -svg = [ + { command = "mpv", args = [ "--mute", "on", "--" ], fork = true, silent = true } ] + +text_default = [ + { command = "micro" }, + { command = "gedit", fork = true, silent = true }, + { command = "bat", confirm_exit = true } ] + +[extension] + +## image formats +bmp.inherit = "image_default" +gif.inherit = "image_default" +heic.inherit = "image_default" +jpeg.inherit = "image_default" +jpe.inherit = "image_default" +jpg.inherit = "image_default" +pgm.inherit = "image_default" +png.inherit = "image_default" +ppm.inherit = "image_default" + +svg.app_list = [ { command = "inkview", fork = true, silent = true }, { command = "inkscape", fork = true, silent = true } ] -tiff = [ +tiff.app_list = [ { command = "qimgv", fork = true, silent = true }, { command = "krita", fork = true, silent = true } ] ## audio formats -flac = [ - { command = "mpv", args = [ "--" ] }, - { command = "mediainfo", confirm_exit = true } ] -m4a = [ - { command = "mpv", args = [ "--" ] }, - { command = "mediainfo", confirm_exit = true } ] -mp3 = [ - { command = "mpv", args = [ "--" ] }, - { command = "mediainfo", confirm_exit = true } ] -ogg = [ - { command = "mpv", args = [ "--" ] }, - { command = "mediainfo", confirm_exit = true } ] -wav = [ - { command = "mpv", args = [ "--" ] }, - { command = "mediainfo", confirm_exit = true } ] -ts = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mpv" }, - { command = "mediainfo", confirm_exit = true } ] +flac.inherit = "audio_default" +m4a.inherit = "audio_default" +mp3.inherit = "audio_default" +ogg.inherit = "audio_default" +wav.inherit = "audio_default" +ts.inherit = "audio_default" ## video formats -avi = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -av1 = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -flv = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -mkv = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -m4v = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -mov = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -mp4 = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -webm = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, -wmv = [ - { command = "mpv", args = [ "--" ] , fork = true, silent = true }, - { command = "mediainfo", confirm_exit = true }, +avi.inherit = "video_default" +av1.inherit = "video_default" +flv.inherit = "video_default" +mkv.inherit = "video_default" +m4v.inherit = "video_default" +mov.inherit = "video_default" +mp4.inherit = "video_default" +webm.inherit = "video_default" +webp.inherit = "video_default" +wmv.inherit = "video_default" ## text formats -build = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -c = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -cmake = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -conf = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -cpp = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -css = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -csv = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -cu = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -eex = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -env = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -ex = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -exs = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -go = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -h = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -hpp = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -hs = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -html = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -ini = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -java = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -js = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -json = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -kt = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -lua = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -log = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -md = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -micro = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -ninja = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -py = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -rkt = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -rs = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -scss = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -sh = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -srt = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -svelte = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -toml = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -tsx = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -txt = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -vim = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -xml = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -yaml = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] -yml = [ - { command = "micro" }, - { command = "gedit", fork = true, silent = true }, - { command = "bat", confirm_exit = true } ] +build.inherit = "text_default" +c.inherit = "text_default" +cmake.inherit = "text_default" +conf.inherit = "text_default" +cpp.inherit = "text_default" +css.inherit = "text_default" +csv.inherit = "text_default" +cu.inherit = "text_default" +eex.inherit = "text_default" +env.inherit = "text_default" +ex.inherit = "text_default" +exs.inherit = "text_default" +go.inherit = "text_default" +h.inherit = "text_default" +hpp.inherit = "text_default" +hs.inherit = "text_default" +html.inherit = "text_default" +ini.inherit = "text_default" +java.inherit = "text_default" +js.inherit = "text_default" +json.inherit = "text_default" +kt.inherit = "text_default" +lua.inherit = "text_default" +log.inherit = "text_default" +md.inherit = "text_default" +micro.inherit = "text_default" +ninja.inherit = "text_default" +py.inherit = "text_default" +rkt.inherit = "text_default" +rs.inherit = "text_default" +scss.inherit = "text_default" +sh.inherit = "text_default" +srt.inherit = "text_default" +svelte.inherit = "text_default" +toml.inherit = "text_default" +tsx.inherit = "text_default" +txt.inherit = "text_default" +vim.inherit = "text_default" +xml.inherit = "text_default" +yaml.inherit = "text_default" +yml.inherit = "text_default" # archive formats -7z = [ +7z.app_list = [ { command = "7z", args = [ "x" ], confirm_exit = true }, { command = "file-roller", fork = true, silent = true } ] -bz2 = [ +bz2.app_list = [ { command = "tar", args = [ "-xvjf" ], confirm_exit = true }, { command = "file-roller", fork = true, silent = true } ] -gz = [ +gz.app_list = [ { command = "tar", args = [ "-xvzf" ], confirm_exit = true }, { command = "file-roller", fork = true, silent = true } ] -tar = [ +tar.app_list = [ { command = "tar", args = [ "-xvf" ], confirm_exit = true }, { command = "file-roller", fork = true, silent = true } ] -tgz = [ +tgz.app_list = [ { command = "tar", args = [ "-xvzf" ], confirm_exit = true }, { command = "file-roller", fork = true, silent = true } ] -rar = [ +rar.app_list = [ { command = "unrar", args = [ "e" ], confirm_exit = true }, { command = "file-roller", fork = true, silent = true } ] -xz = [ +xz.app_list = [ { command = "tar", args = [ "-xvJf" ], confirm_exit = true }, { command = "file-roller", fork = true, silent = true } ] -zip = [ +zip.app_list = [ { command = "unzip", confirm_exit = true }, { command = "file-roller", fork = true, silent = true } ] # misc formats -aup = [ +aup.app_list = [ { command = "audacity", fork = true, silent = true } ] -m3u = [ +m3u.app_list = [ { command = "micro" }, { command = "mpv" }, { command = "gedit", fork = true, silent = true }, { command = "bat", confirm_exit = true } ] -docx = [ +docx.app_list = [ { command = "libreoffice", fork = true, silent = true } ] -odt = [ +odt.app_list = [ { command = "libreoffice", fork = true, silent = true } ] -odf = [ +odf.app_list = [ { command = "libreoffice", fork = true, silent = true } ] -pdf = [ +pdf.app_list = [ { command = "evince", fork = true, silent = true } ] -tex = [ +tex.app_list = [ { command = "micro" }, { command = "gedit", fork = true, silent = true }, { command = "bat", confirm_exit = true }, { command = "pdflatex" } ] -torrent = [ +torrent.app_list = [ { command = "transmission-gtk" } ] diff -ur a/config/theme.toml b/config/theme.toml --- a/config/theme.toml 2021-05-08 03:04:44.000000000 +0300 +++ b/config/theme.toml 2021-08-07 11:25:14.695474018 +0300 @@ -34,6 +34,8 @@ fg = "yellow" [ext.png] fg = "yellow" +[ext.webp] +fg = "yellow" [ext.ppm] fg = "yellow" [ext.svg] Только в b: .git Только в b/.github/workflows: rust-linux-dev.yml Только в b/.github/workflows: rust-linux-main.yml Только в a/.github/workflows: rust-linux.yml Только в b/.github/workflows: rust-macos-dev.yml Только в b/.github/workflows: rust-macos-main.yml Только в a/.github/workflows: rust-macos.yml diff -ur a/README.md b/README.md --- a/README.md 2021-05-08 03:04:44.000000000 +0300 +++ b/README.md 2021-08-07 11:25:14.695474018 +0300 @@ -1,6 +1,6 @@ -[![Linux Build](https://github.com/kamiyaa/joshuto/actions/workflows/rust-linux.yml/badge.svg)](https://github.com/kamiyaa/joshuto/actions/workflows/rust-linux.yml) +[![Linux build](https://github.com/kamiyaa/joshuto/actions/workflows/rust-linux-main.yml/badge.svg)](https://github.com/kamiyaa/joshuto/actions/workflows/rust-linux-main.yml) -[![MacOS Build](https://github.com/kamiyaa/joshuto/actions/workflows/rust-macos.yml/badge.svg)](https://github.com/kamiyaa/joshuto/actions/workflows/rust-macos.yml) +[![MacOS build](https://github.com/kamiyaa/joshuto/actions/workflows/rust-macos-main.yml/badge.svg)](https://github.com/kamiyaa/joshuto/actions/workflows/rust-macos-main.yml) # joshuto @@ -45,6 +45,18 @@ sudo dnf install joshuto ``` +##### Arch ([AUR](https://aur.archlinux.org)) + +* [release](https://aur.archlinux.org/packages/joshuto) +``` +[yay/paru] -S joshuto +``` + +* [build from source](https://aur.archlinux.org/packages/joshuto-git) +``` +[yay/paru] -S joshuto-git +``` + ## Usage ``` @@ -53,30 +65,24 @@ ## Configuration -Place config files inside `$XDG_CONFIG_HOME/joshuto` (usually `$HOME/.config/joshuto/` for GNU/Linux). - -Joshuto can currently be configured using the following files: +Check out [wiki/Configuration](https://github.com/kamiyaa/joshuto/wiki/Configuration) for details +and [config/](config/) for examples #### [joshuto.toml](config/joshuto.toml) - - general configurations #### [keymap.toml](/config/keymap.toml) - - for keybindings, please take a look at [src/util/key_mapping.rs](/src/util/key_mapping.rs#L18) for non-printable keys - for commands, please take a look at [src/commands/commands.rs](/src/commands/commands.rs#L139) #### [mimetype.toml](/config/mimetype.toml) - - for opening files with applications #### [theme.toml](/config/theme.toml) - - color customizations ## Contributing - -Please create a pull request :) +See [wiki/Contributing](https://github.com/kamiyaa/joshuto/wiki/Contributing) ## Features/Bugs @@ -87,8 +93,10 @@ - [x] Migrate to [tui-rs](https://github.com/fdehau/tui-rs) - [x] Tab support - [x] Ctrl/Shift/Alt support -- [x] Asynch File IO (cut/copy/paste/delete/rename) (in progress) +- [x] Asynch File IO (cut/copy/paste) - [ ] Built-in command line (in progress) + - Currently implementation is kind of janky - [ ] File previews (in progress) + - Waiting for tui-rs to support parsing ANSI color codes - [ ] Tab autocomplete (in progress) - [x] Bulk rename diff -ur a/src/commands/bulk_rename.rs b/src/commands/bulk_rename.rs --- a/src/commands/bulk_rename.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/bulk_rename.rs 2021-08-07 11:25:14.702140722 +0300 @@ -81,6 +81,9 @@ )); } + println!("{}", termion::clear::All); + termion::cursor::Goto(0, 0); + for (p, q) in paths.iter().zip(paths_renamed.iter()) { println!("{:?} -> {:?}", p, q); } diff -ur a/src/commands/change_directory.rs b/src/commands/change_directory.rs --- a/src/commands/change_directory.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/change_directory.rs 2021-08-07 11:25:14.702140722 +0300 @@ -3,7 +3,6 @@ use crate::context::AppContext; use crate::error::JoshutoResult; use crate::history::DirectoryHistory; -use crate::util::load_child::LoadChild; pub fn cd(path: &path::Path, context: &mut AppContext) -> std::io::Result<()> { std::env::set_current_dir(path)?; @@ -25,6 +24,5 @@ pub fn change_directory(context: &mut AppContext, path: &path::Path) -> JoshutoResult<()> { _change_directory(path, context)?; - LoadChild::load_child(context)?; Ok(()) } diff -ur a/src/commands/command_line.rs b/src/commands/command_line.rs --- a/src/commands/command_line.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/command_line.rs 2021-08-07 11:25:14.702140722 +0300 @@ -1,3 +1,5 @@ +use std::str::FromStr; + use crate::commands::KeyCommand; use crate::context::AppContext; use crate::error::JoshutoResult; @@ -21,7 +23,7 @@ if let Some(s) = user_input { let trimmed = s.trim_start(); - let command = KeyCommand::parse_command(trimmed)?; + let command = KeyCommand::from_str(trimmed)?; command.execute(context, backend) } else { Ok(()) diff -ur a/src/commands/cursor_move.rs b/src/commands/cursor_move.rs --- a/src/commands/cursor_move.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/cursor_move.rs 2021-08-07 11:25:14.702140722 +0300 @@ -1,7 +1,7 @@ use crate::context::AppContext; use crate::error::JoshutoResult; -use crate::history::DirectoryHistory; use crate::ui::TuiBackend; + use std::path::PathBuf; pub fn cursor_move(new_index: usize, context: &mut AppContext) -> JoshutoResult<()> { @@ -20,18 +20,6 @@ path = Some(entry.file_path().to_path_buf()) } } - - // get preview - if let Some(path) = path { - if path.is_dir() { - let options = context.config_ref().display_options_ref().clone(); - context - .tab_context_mut() - .curr_tab_mut() - .history_mut() - .create_or_soft_update(path.as_path(), &options)?; - } - } Ok(()) } @@ -96,18 +84,33 @@ Ok(()) } -pub fn page_up(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult<()> { - let half_page = { - match backend.terminal.as_ref().unwrap().size() { - Ok(rect) => rect.height as usize - 2, - _ => 10, +fn get_page_size(context: &AppContext, backend: &TuiBackend) -> Option { + let config = context.config_ref(); + let rect = backend.terminal.as_ref().map(|t| t.size())?.ok()?; + + let rect_height = rect.height as usize; + if config.display_options_ref().show_borders() { + if rect_height >= 4 { + Some(rect_height - 4) + } else { + None + } + } else { + if rect_height >= 2 { + Some(rect_height - 2) + } else { + None } - }; + } +} + +pub fn page_up(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult<()> { + let page_size = get_page_size(context, backend).unwrap_or(10); let movement = match context.tab_context_ref().curr_tab_ref().curr_list_ref() { Some(curr_list) => curr_list .index - .map(|idx| if idx > half_page { idx - half_page } else { 0 }), + .map(|idx| if idx > page_size { idx - page_size } else { 0 }), None => None, }; @@ -118,21 +121,16 @@ } pub fn page_down(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult<()> { - let half_page = { - match backend.terminal.as_ref().unwrap().size() { - Ok(rect) => rect.height as usize - 2, - _ => 10, - } - }; + let page_size = get_page_size(context, backend).unwrap_or(10); let movement = match context.tab_context_ref().curr_tab_ref().curr_list_ref() { Some(curr_list) => { let dir_len = curr_list.len(); curr_list.index.map(|idx| { - if idx + half_page > dir_len - 1 { + if idx + page_size > dir_len - 1 { dir_len - 1 } else { - idx + half_page + idx + page_size } }) } diff -ur a/src/commands/delete_files.rs b/src/commands/delete_files.rs --- a/src/commands/delete_files.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/delete_files.rs 2021-08-07 11:25:14.702140722 +0300 @@ -7,7 +7,6 @@ use crate::history::DirectoryHistory; use crate::ui::widgets::TuiPrompt; use crate::ui::TuiBackend; -use crate::util::load_child::LoadChild; use super::reload; @@ -116,6 +115,5 @@ for tab in context.tab_context_mut().iter_mut() { tab.history_mut().reload(&curr_path, &options)?; } - LoadChild::load_child(context)?; Ok(()) } diff -ur a/src/commands/file_ops.rs b/src/commands/file_ops.rs --- a/src/commands/file_ops.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/file_ops.rs 2021-08-07 11:25:14.702140722 +0300 @@ -49,45 +49,76 @@ } pub fn copy_filename(context: &mut AppContext) -> JoshutoResult<()> { - let entry_file_name = match context + let entry_file_name = context .tab_context_ref() .curr_tab_ref() .curr_list_ref() .and_then(|c| c.curr_entry_ref()) - { - Some(entry) => Some(entry.file_name().to_string()), - None => None, - }; + .map(|entry| entry.file_name().to_string()); + if let Some(file_name) = entry_file_name { - let clipboards = [ - ( - "wl-copy", - format!("printf '%s' {} | {} 2> /dev/null", file_name, "wl-copy"), - ), - ( - "xsel", - format!("printf '%s' {} | {} -ib 2> /dev/null", file_name, "xsel"), - ), - ( - "xclip", - format!( - "printf '%s' {} | {} -selection clipboard 2> /dev/null", - file_name, "xclip" - ), + copy_string_to_buffer(file_name)?; + } + Ok(()) +} + +pub fn copy_filepath(context: &mut AppContext) -> JoshutoResult<()> { + let entry_file_path = context + .tab_context_ref() + .curr_tab_ref() + .curr_list_ref() + .and_then(|c| c.curr_entry_ref()) + .and_then(|entry| entry.file_path().to_str()) + .map(|s| s.to_string()); + + if let Some(file_path) = entry_file_path { + copy_string_to_buffer(file_path)?; + } + Ok(()) +} + +pub fn copy_dirname(context: &mut AppContext) -> JoshutoResult<()> { + let opt_entry = context + .tab_context_ref() + .curr_tab_ref() + .curr_list_ref() + .map(|dirlist| dirlist.file_path()); + + if let Some(pathbuf) = opt_entry { + if let Some(dir) = pathbuf.to_str().map(String::from) { + copy_string_to_buffer(dir)? + } + }; + Ok(()) +} + +fn copy_string_to_buffer(string: String) -> JoshutoResult<()> { + let clipboards = [ + ( + "wl-copy", + format!("printf '%s' {} | {} 2> /dev/null", string, "wl-copy"), + ), + ( + "xsel", + format!("printf '%s' {} | {} -ib 2> /dev/null", string, "xsel"), + ), + ( + "xclip", + format!( + "printf '%s' {} | {} -selection clipboard 2> /dev/null", + string, "xclip" ), - ]; + ), + ]; - for (_, command) in clipboards.iter() { - match Command::new("sh").args(&["-c", command.as_str()]).status() { - Ok(s) if s.success() => return Ok(()), - _ => {} - } + for (_, command) in clipboards.iter() { + match Command::new("sh").args(&["-c", command.as_str()]).status() { + Ok(s) if s.success() => return Ok(()), + _ => {} } - let err = Err(JoshutoError::new( - JoshutoErrorKind::ClipboardError, - "Failed to copy to clipboard".to_string(), - )); - return err; } - Ok(()) + Err(JoshutoError::new( + JoshutoErrorKind::ClipboardError, + "Failed to copy to clipboard".to_string(), + )) } diff -ur a/src/commands/key_command.rs b/src/commands/key_command.rs --- a/src/commands/key_command.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/key_command.rs 2021-08-07 11:25:14.702140722 +0300 @@ -4,7 +4,6 @@ use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult}; use crate::io::IoWorkerOptions; use crate::ui::TuiBackend; -use crate::util::load_child::LoadChild; use crate::util::select::SelectOption; use crate::util::sort::SortType; @@ -24,6 +23,8 @@ CopyFiles, PasteFiles(IoWorkerOptions), CopyFileName, + CopyFilePath, + CopyDirName, CursorMoveUp(usize), CursorMoveDown(usize), @@ -40,7 +41,7 @@ DeleteFiles, NewDirectory(path::PathBuf), OpenFile, - OpenFileWith, + OpenFileWith(Option), ParentDirectory, Quit, @@ -49,15 +50,17 @@ RenameFile(path::PathBuf), RenameFileAppend, RenameFilePrepend, + TouchFile(String), SearchGlob(String), SearchString(String), + SearchSkim, SearchNext, SearchPrev, SelectFiles(String, SelectOption), SetMode, - ShellCommand(Vec), + SubProcess(Vec, bool), ShowWorkers, ToggleHiddenFiles, @@ -77,12 +80,14 @@ Self::ChangeDirectory(_) => "cd", Self::NewTab => "new_tab", Self::CloseTab => "close_tab", - Self::CommandLine(_, _) => "console", + Self::CommandLine(_, _) => ":", Self::CutFiles => "cut_files", Self::CopyFiles => "copy_files", Self::PasteFiles(_) => "paste_files", Self::CopyFileName => "copy_filename", + Self::CopyFilePath => "copy_filepath", + Self::CopyDirName => "copy_dirname", Self::CursorMoveUp(_) => "cursor_move_up", Self::CursorMoveDown(_) => "cursor_move_down", @@ -95,26 +100,29 @@ Self::ParentCursorMoveDown(_) => "parent_cursor_move_down", Self::DeleteFiles => "delete_files", - Self::NewDirectory(_) => "new_directory", + Self::NewDirectory(_) => "mkdir", Self::OpenFile => "open", - Self::OpenFileWith => "open_with", + Self::OpenFileWith(_) => "open_with", Self::ParentDirectory => "cd ..", Self::Quit => "quit", Self::ForceQuit => "force_quit", Self::ReloadDirList => "reload_dirlist", - Self::RenameFile(_) => "rename_file", + Self::RenameFile(_) => "rename", + Self::TouchFile(_) => "touch", Self::RenameFileAppend => "rename_append", Self::RenameFilePrepend => "rename_prepend", Self::SearchString(_) => "search", Self::SearchGlob(_) => "search_glob", + Self::SearchSkim => "search_skim", Self::SearchNext => "search_next", Self::SearchPrev => "search_prev", Self::SelectFiles(_, _) => "select", Self::SetMode => "set_mode", - Self::ShellCommand(_) => "shell", + Self::SubProcess(_, false) => "shell", + Self::SubProcess(_, true) => "spawn", Self::ShowWorkers => "show_workers", Self::ToggleHiddenFiles => "toggle_hidden", @@ -125,8 +133,12 @@ Self::TabSwitch(_) => "tab_switch", } } +} + +impl std::str::FromStr for KeyCommand { + type Err = JoshutoError; - pub fn parse_command(s: &str) -> JoshutoResult { + fn from_str(s: &str) -> Result { if let Some(stripped) = s.strip_prefix(':') { return Ok(Self::CommandLine(stripped.to_owned(), "".to_owned())); } @@ -155,6 +167,8 @@ "close_tab" => Ok(Self::CloseTab), "copy_files" => Ok(Self::CopyFiles), "copy_filename" => Ok(Self::CopyFileName), + "copy_filepath" => Ok(Self::CopyFilePath), + "copy_dirname" => Ok(Self::CopyDirName), "cursor_move_home" => Ok(Self::CursorMoveHome), "cursor_move_end" => Ok(Self::CursorMoveEnd), "cursor_move_page_up" => Ok(Self::CursorMovePageUp), @@ -206,16 +220,24 @@ if arg.is_empty() { Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, - format!("{}: missing additional parameter", command), + format!("{}: no directory name given", command), )) } else { Ok(Self::NewDirectory(path::PathBuf::from(arg))) } } "new_tab" => Ok(Self::NewTab), - - "open_file" => Ok(Self::OpenFile), - "open_file_with" => Ok(Self::OpenFileWith), + "open" => Ok(Self::OpenFile), + "open_with" => match arg { + "" => Ok(Self::OpenFileWith(None)), + arg => match arg.trim().parse::() { + Ok(s) => Ok(Self::OpenFileWith(Some(s))), + Err(e) => Err(JoshutoError::new( + JoshutoErrorKind::ParseError, + e.to_string(), + )), + }, + }, "paste_files" => { let mut options = IoWorkerOptions::default(); for arg in arg.split_whitespace() { @@ -246,6 +268,7 @@ Ok(Self::RenameFile(path)) } }, + "touch" => Ok(Self::TouchFile(arg.to_string())), "rename_append" => Ok(Self::RenameFileAppend), "rename_prepend" => Ok(Self::RenameFilePrepend), "search" => match arg { @@ -262,6 +285,7 @@ )), arg => Ok(Self::SearchGlob(arg.to_string())), }, + "search_skim" => Ok(Self::SearchSkim), "search_next" => Ok(Self::SearchNext), "search_prev" => Ok(Self::SearchPrev), "select" => { @@ -289,11 +313,11 @@ } } "set_mode" => Ok(Self::SetMode), - "shell" => match shell_words::split(arg) { - Ok(s) if !s.is_empty() => Ok(Self::ShellCommand(s)), + "shell" | "spawn" => match shell_words::split(arg) { + Ok(s) if !s.is_empty() => Ok(Self::SubProcess(s, command == "spawn")), Ok(_) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, - format!("sort: args {}", arg), + format!("{}: No commands given", command), )), Err(e) => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, @@ -307,7 +331,7 @@ Some(s) => Ok(Self::Sort(s)), None => Err(JoshutoError::new( JoshutoErrorKind::InvalidParameters, - format!("sort: Unknown option {}", arg), + format!("{}: Unknown option '{}'", command, arg), )), }, }, @@ -321,7 +345,7 @@ "toggle_hidden" => Ok(Self::ToggleHiddenFiles), inp => Err(JoshutoError::new( JoshutoErrorKind::UnrecognizedCommand, - format!("Unknown command: {}", inp), + format!("Unrecognized command '{}'", inp), )), } } @@ -333,7 +357,6 @@ Self::BulkRename => bulk_rename::bulk_rename(context, backend), Self::ChangeDirectory(p) => { change_directory::change_directory(context, p.as_path())?; - LoadChild::load_child(context)?; Ok(()) } Self::NewTab => tab_ops::new_tab(context), @@ -345,6 +368,8 @@ Self::CopyFiles => file_ops::copy(context), Self::PasteFiles(options) => file_ops::paste(context, options.clone()), Self::CopyFileName => file_ops::copy_filename(context), + Self::CopyFilePath => file_ops::copy_filepath(context), + Self::CopyDirName => file_ops::copy_dirname(context), Self::CursorMoveUp(u) => cursor_move::up(context, *u), Self::CursorMoveDown(u) => cursor_move::down(context, *u), @@ -362,7 +387,8 @@ } Self::NewDirectory(p) => new_directory::new_directory(context, p.as_path()), Self::OpenFile => open_file::open(context, backend), - Self::OpenFileWith => open_file::open_with(context, backend), + Self::OpenFileWith(None) => open_file::open_with_interactive(context, backend), + Self::OpenFileWith(Some(i)) => open_file::open_with_index(context, backend, *i), Self::ParentDirectory => parent_directory::parent_directory(context), Self::Quit => quit::quit(context), @@ -371,8 +397,10 @@ Self::RenameFile(p) => rename_file::rename_file(context, p.as_path()), Self::RenameFileAppend => rename_file::rename_file_append(context, backend), Self::RenameFilePrepend => rename_file::rename_file_prepend(context, backend), + Self::TouchFile(arg) => touch_file::touch_file(context, arg.as_str()), Self::SearchGlob(pattern) => search_glob::search_glob(context, pattern.as_str()), Self::SearchString(pattern) => search_string::search_string(context, pattern.as_str()), + Self::SearchSkim => search_skim::search_skim(context, backend), Self::SearchNext => search::search_next(context), Self::SearchPrev => search::search_prev(context), @@ -380,7 +408,9 @@ selection::select_files(context, pattern.as_str(), &options) } Self::SetMode => set_mode::set_mode(context, backend), - Self::ShellCommand(v) => shell::shell(context, backend, v.as_slice()), + Self::SubProcess(v, spawn) => { + sub_process::sub_process(context, backend, v.as_slice(), *spawn) + } Self::ShowWorkers => show_workers::show_workers(context, backend), Self::ToggleHiddenFiles => show_hidden::toggle_hidden(context), @@ -415,7 +445,7 @@ Self::SelectFiles(pattern, options) => { write!(f, "{} {} {}", self.command(), pattern, options) } - Self::ShellCommand(c) => write!(f, "{} {:?}", self.command(), c), + Self::SubProcess(c, _) => write!(f, "{} {:?}", self.command(), c), Self::Sort(t) => write!(f, "{} {}", self.command(), t), Self::TabSwitch(i) => write!(f, "{} {}", self.command(), i), _ => write!(f, "{}", self.command()), diff -ur a/src/commands/mod.rs b/src/commands/mod.rs --- a/src/commands/mod.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/mod.rs 2021-08-07 11:25:14.702140722 +0300 @@ -13,14 +13,16 @@ pub mod rename_file; pub mod search; pub mod search_glob; +pub mod search_skim; pub mod search_string; pub mod selection; pub mod set_mode; -pub mod shell; pub mod show_hidden; pub mod show_workers; pub mod sort; +pub mod sub_process; pub mod tab_ops; +pub mod touch_file; pub mod command_keybind; pub mod key_command; diff -ur a/src/commands/new_directory.rs b/src/commands/new_directory.rs --- a/src/commands/new_directory.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/new_directory.rs 2021-08-07 11:25:14.702140722 +0300 @@ -3,7 +3,6 @@ use crate::context::AppContext; use crate::error::JoshutoResult; use crate::history::DirectoryHistory; -use crate::util::load_child::LoadChild; pub fn new_directory(context: &mut AppContext, p: &path::Path) -> JoshutoResult<()> { std::fs::create_dir_all(p)?; @@ -12,6 +11,5 @@ for tab in context.tab_context_mut().iter_mut() { tab.history_mut().reload(&curr_path, &options)?; } - LoadChild::load_child(context)?; Ok(()) } diff -ur a/src/commands/open_file.rs b/src/commands/open_file.rs --- a/src/commands/open_file.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/open_file.rs 2021-08-07 11:25:14.702140722 +0300 @@ -6,7 +6,6 @@ use crate::error::{JoshutoError, JoshutoErrorKind, JoshutoResult}; use crate::ui::views::TuiTextField; use crate::ui::TuiBackend; -use crate::util::load_child::LoadChild; use super::change_directory; @@ -16,7 +15,7 @@ let mut options: Vec<&AppMimetypeEntry> = Vec::new(); if let Some(file_ext) = path.extension() { if let Some(file_ext) = file_ext.to_str() { - let ext_entries = MIMETYPE_T.get_entries_for_ext(file_ext); + let ext_entries = MIMETYPE_T.app_list_for_ext(file_ext); options.extend(ext_entries); } } @@ -24,6 +23,8 @@ } pub fn open(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult<()> { + let config = context.config_ref(); + if let Some(entry) = context .tab_context_ref() .curr_tab_ref() @@ -33,7 +34,6 @@ if entry.file_path().is_dir() { let path = entry.file_path().to_path_buf(); change_directory::cd(path.as_path(), context)?; - LoadChild::load_child(context)?; } else { let paths = context .tab_context_ref() @@ -48,6 +48,7 @@ )); } let files: Vec<&std::ffi::OsStr> = paths.iter().filter_map(|e| e.file_name()).collect(); + let options = get_options(paths[0].as_path()); if !options.is_empty() { @@ -59,6 +60,10 @@ backend.terminal_restore()?; res?; } + } else if config.xdg_open { + backend.terminal_drop(); + open::that(paths[0].as_path())?; + backend.terminal_restore()?; } else { open_with_helper(context, backend, options, files)?; } @@ -133,7 +138,10 @@ } } -pub fn open_with(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult<()> { +pub fn open_with_interactive( + context: &mut AppContext, + backend: &mut TuiBackend, +) -> JoshutoResult<()> { let paths = context .tab_context_ref() .curr_tab_ref() @@ -152,3 +160,43 @@ open_with_helper(context, backend, options, files)?; Ok(()) } + +pub fn open_with_index( + context: &mut AppContext, + backend: &mut TuiBackend, + index: usize, +) -> JoshutoResult<()> { + let paths = context + .tab_context_ref() + .curr_tab_ref() + .curr_list_ref() + .map_or(vec![], |s| s.get_selected_paths()); + + if paths.is_empty() { + return Err(JoshutoError::new( + JoshutoErrorKind::Io(io::ErrorKind::NotFound), + String::from("No files selected"), + )); + } + let files: Vec<&std::ffi::OsStr> = paths.iter().filter_map(|e| e.file_name()).collect(); + let options = get_options(paths[0].as_path()); + + if index >= options.len() { + return Err(JoshutoError::new( + JoshutoErrorKind::Io(std::io::ErrorKind::InvalidData), + "option does not exist".to_string(), + )); + } + + let mimetype_entry = &options[index]; + if mimetype_entry.get_fork() { + mimetype_entry.execute_with(files.as_slice())?; + Ok(()) + } else { + backend.terminal_drop(); + let res = mimetype_entry.execute_with(files.as_slice()); + backend.terminal_restore()?; + res?; + Ok(()) + } +} diff -ur a/src/commands/parent_directory.rs b/src/commands/parent_directory.rs --- a/src/commands/parent_directory.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/parent_directory.rs 2021-08-07 11:25:14.702140722 +0300 @@ -1,6 +1,5 @@ use crate::context::AppContext; use crate::error::JoshutoResult; -use crate::util::load_child::LoadChild; use super::reload; @@ -24,6 +23,5 @@ pub fn parent_directory(context: &mut AppContext) -> JoshutoResult<()> { parent_directory_helper(context)?; reload::soft_reload(context.tab_context_ref().index, context)?; - LoadChild::load_child(context)?; Ok(()) } diff -ur a/src/commands/reload.rs b/src/commands/reload.rs --- a/src/commands/reload.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/reload.rs 2021-08-07 11:25:14.705474074 +0300 @@ -1,6 +1,5 @@ use crate::context::AppContext; use crate::error::JoshutoResult; -use crate::util::load_child::LoadChild; pub fn soft_reload(index: usize, context: &mut AppContext) -> std::io::Result<()> { let options = context.config_ref().display_options_ref().clone(); @@ -42,6 +41,5 @@ pub fn reload_dirlist(context: &mut AppContext) -> JoshutoResult<()> { reload(context, context.tab_context_ref().index)?; - LoadChild::load_child(context)?; Ok(()) } diff -ur a/src/commands/rename_file.rs b/src/commands/rename_file.rs --- a/src/commands/rename_file.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/rename_file.rs 2021-08-07 11:25:14.705474074 +0300 @@ -3,7 +3,6 @@ use crate::context::AppContext; use crate::error::JoshutoResult; use crate::ui::TuiBackend; -use crate::util::load_child::LoadChild; use super::command_line; @@ -31,12 +30,11 @@ .curr_tab_ref() .curr_list_ref() .and_then(|s| s.curr_entry_ref()) - .and_then(|s| Some(s.file_path().to_path_buf())); + .map(|s| s.file_path().to_path_buf()); if let Some(path) = path { _rename_file(context, path.as_path(), dest)?; } - LoadChild::load_child(context)?; Ok(()) } Только в b/src/commands: search_skim.rs diff -ur a/src/commands/set_mode.rs b/src/commands/set_mode.rs --- a/src/commands/set_mode.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/set_mode.rs 2021-08-07 11:25:14.705474074 +0300 @@ -1,3 +1,5 @@ +use std::fs; + use crate::context::AppContext; use crate::error::JoshutoResult; use crate::ui::views::TuiTextField; @@ -9,6 +11,7 @@ #[derive(Clone, Debug)] pub struct SetMode; +#[cfg(unix)] const LIBC_PERMISSION_VALS: [(libc::mode_t, char); 9] = [ (libc::S_IRUSR, 'r'), (libc::S_IWUSR, 'w'), @@ -23,7 +26,7 @@ pub fn str_to_mode(s: &str) -> u32 { let mut mode: u32 = 0; - for (i, ch) in s.chars().enumerate() { + for (i, ch) in s.chars().enumerate().take(LIBC_PERMISSION_VALS.len()) { if ch == LIBC_PERMISSION_VALS[i].1 { let val: u32 = LIBC_PERMISSION_VALS[i].0 as u32; mode |= val; @@ -33,6 +36,7 @@ } pub fn set_mode(context: &mut AppContext, backend: &mut TuiBackend) -> JoshutoResult<()> { + #[cfg(unix)] use std::os::unix::fs::PermissionsExt; const PREFIX: &str = "set_mode "; @@ -44,9 +48,10 @@ let user_input = match entry { Some(entry) => { - context.flush_event(); let mode = entry.metadata.permissions_ref().mode(); let mode_string = unix::mode_to_string(mode); + + context.flush_event(); TuiTextField::default() .prompt(":") .prefix(PREFIX) @@ -58,19 +63,28 @@ if let Some(s) = user_input { if let Some(stripped) = s.strip_prefix(PREFIX) { - let s = stripped; - let mode = str_to_mode(s); - - let entry = context - .tab_context_mut() - .curr_tab_mut() - .curr_list_mut() - .and_then(|x| x.curr_entry_mut()) - .unwrap(); - - unix::set_mode(entry.file_path(), mode); - entry.metadata.permissions_mut().set_mode(mode); - cursor_move::down(context, 1)?; + let mode = str_to_mode(stripped); + if let Some(curr_list) = context.tab_context_mut().curr_tab_mut().curr_list_mut() { + if curr_list.any_selected() { + for entry in curr_list.iter_selected_mut() { + let mut permissions = entry.metadata.permissions_ref().clone(); + let file_mode = (permissions.mode() >> 12) << 12 | mode; + permissions.set_mode(file_mode); + + fs::set_permissions(entry.file_path(), permissions)?; + entry.metadata.permissions_mut().set_mode(file_mode); + } + } else if let Some(entry) = curr_list.curr_entry_mut() { + let mut permissions = entry.metadata.permissions_ref().clone(); + let file_mode = (permissions.mode() >> 12) << 12 | mode; + permissions.set_mode(file_mode); + + fs::set_permissions(entry.file_path(), permissions)?; + entry.metadata.permissions_mut().set_mode(file_mode); + + cursor_move::down(context, 1)?; + } + } } } Ok(()) Только в a/src/commands: shell.rs diff -ur a/src/commands/sort.rs b/src/commands/sort.rs --- a/src/commands/sort.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/sort.rs 2021-08-07 11:25:14.705474074 +0300 @@ -1,22 +1,19 @@ use crate::context::AppContext; use crate::error::JoshutoResult; use crate::history::DirectoryHistory; - -use crate::util::load_child::LoadChild; use crate::util::sort::SortType; use super::reload; pub fn set_sort(context: &mut AppContext, method: SortType) -> JoshutoResult<()> { - context.config_mut().sort_options_mut().sort_method = method; - + context + .config_mut() + .sort_options_mut() + .set_sort_method(method); for tab in context.tab_context_mut().iter_mut() { tab.history_mut().depreciate_all_entries(); } - - reload::soft_reload(context.tab_context_ref().index, context)?; - LoadChild::load_child(context)?; - Ok(()) + refresh(context) } pub fn toggle_reverse(context: &mut AppContext) -> JoshutoResult<()> { @@ -26,7 +23,10 @@ for tab in context.tab_context_mut().iter_mut() { tab.history_mut().depreciate_all_entries(); } + refresh(context) +} + +fn refresh(context: &mut AppContext) -> JoshutoResult<()> { reload::soft_reload(context.tab_context_ref().index, context)?; - LoadChild::load_child(context)?; Ok(()) } Только в b/src/commands: sub_process.rs diff -ur a/src/commands/tab_ops.rs b/src/commands/tab_ops.rs --- a/src/commands/tab_ops.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/commands/tab_ops.rs 2021-08-07 11:25:14.705474074 +0300 @@ -3,8 +3,7 @@ use crate::context::AppContext; use crate::error::JoshutoResult; use crate::history::DirectoryHistory; -use crate::tab::JoshutoTab; -use crate::util::load_child::LoadChild; +use crate::tab::{JoshutoTab, TabHomePage}; use crate::HOME_DIR; @@ -51,19 +50,25 @@ _tab_switch(new_index, context) } +pub fn new_tab_home_path(context: &AppContext) -> path::PathBuf { + match context.config_ref().tab_options_ref().home_page() { + TabHomePage::Home => match HOME_DIR.as_ref() { + Some(s) => s.clone(), + None => path::PathBuf::from("/"), + }, + TabHomePage::Inherit => context.tab_context_ref().curr_tab_ref().cwd().to_path_buf(), + TabHomePage::Root => path::PathBuf::from("/"), + } +} + pub fn new_tab(context: &mut AppContext) -> JoshutoResult<()> { - /* start the new tab in $HOME or root */ - let curr_path = match HOME_DIR.as_ref() { - Some(s) => s.clone(), - None => path::PathBuf::from("/"), - }; + let new_tab_path = new_tab_home_path(context); - let tab = JoshutoTab::new(curr_path, context.config_ref().display_options_ref())?; + let tab = JoshutoTab::new(new_tab_path, context.config_ref().display_options_ref())?; context.tab_context_mut().push_tab(tab); let new_index = context.tab_context_ref().len() - 1; context.tab_context_mut().index = new_index; _tab_switch(new_index, context)?; - LoadChild::load_child(context)?; Ok(()) } Только в b/src/commands: touch_file.rs diff -ur a/src/config/default/config.rs b/src/config/default/config.rs --- a/src/config/default/config.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/default/config.rs 2021-08-07 11:25:14.705474074 +0300 @@ -1,6 +1,9 @@ use serde_derive::Deserialize; +use super::preview::{PreviewOption, PreviewRawOption}; +use super::tab::{TabOption, TabRawOption}; use super::DisplayRawOption; + use crate::config::{parse_to_config_file, ConfigStructure, Flattenable}; use crate::util::display::DisplayOption; use crate::util::sort; @@ -11,9 +14,6 @@ const fn default_scroll_offset() -> usize { 6 } -const fn default_max_preview_size() -> u64 { - 2 * 1024 * 1024 // 2 MB -} #[derive(Clone, Debug, Deserialize)] pub struct RawAppConfig { @@ -23,31 +23,35 @@ use_trash: bool, #[serde(default)] xdg_open: bool, - #[serde(default = "default_max_preview_size")] - max_preview_size: u64, #[serde(default, rename = "display")] display_options: DisplayRawOption, + #[serde(default, rename = "preview")] + preview_options: PreviewRawOption, + #[serde(default, rename = "tab")] + tab_options: TabRawOption, } impl Flattenable for RawAppConfig { fn flatten(self) -> AppConfig { AppConfig { - max_preview_size: self.max_preview_size, scroll_offset: self.scroll_offset, use_trash: self.use_trash, xdg_open: self.xdg_open, _display_options: self.display_options.flatten(), + _preview_options: self.preview_options.flatten(), + _tab_options: self.tab_options.flatten(), } } } #[derive(Debug, Clone)] pub struct AppConfig { - pub max_preview_size: u64, pub scroll_offset: usize, pub use_trash: bool, pub xdg_open: bool, _display_options: DisplayOption, + _preview_options: PreviewOption, + _tab_options: TabOption, } impl AppConfig { @@ -58,12 +62,26 @@ &mut self._display_options } + pub fn preview_options_ref(&self) -> &PreviewOption { + &self._preview_options + } + pub fn preview_options_mut(&mut self) -> &mut PreviewOption { + &mut self._preview_options + } + pub fn sort_options_ref(&self) -> &sort::SortOption { self.display_options_ref().sort_options_ref() } pub fn sort_options_mut(&mut self) -> &mut sort::SortOption { self.display_options_mut().sort_options_mut() } + + pub fn tab_options_ref(&self) -> &TabOption { + &self._tab_options + } + pub fn tab_options_mut(&mut self) -> &mut TabOption { + &mut self._tab_options + } } impl ConfigStructure for AppConfig { @@ -75,11 +93,12 @@ impl std::default::Default for AppConfig { fn default() -> Self { Self { - max_preview_size: default_max_preview_size(), scroll_offset: default_scroll_offset(), use_trash: true, xdg_open: false, _display_options: DisplayOption::default(), + _preview_options: PreviewOption::default(), + _tab_options: TabOption::default(), } } } diff -ur a/src/config/default/mod.rs b/src/config/default/mod.rs --- a/src/config/default/mod.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/default/mod.rs 2021-08-07 11:25:14.705474074 +0300 @@ -1,7 +1,11 @@ pub mod config; pub mod display; +pub mod preview; pub mod sort; +pub mod tab; pub use self::config::AppConfig; pub use self::display::DisplayRawOption; +pub use self::preview::{PreviewOption, PreviewRawOption}; pub use self::sort::SortRawOption; +pub use self::tab::{TabOption, TabRawOption}; Только в b/src/config/default: preview.rs diff -ur a/src/config/default/sort.rs b/src/config/default/sort.rs --- a/src/config/default/sort.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/default/sort.rs 2021-08-07 11:25:14.705474074 +0300 @@ -24,11 +24,15 @@ Some(s) => sort::SortType::parse(s).unwrap_or(sort::SortType::Natural), None => sort::SortType::Natural, }; + + let mut sort_methods = sort::SortTypes::default(); + sort_methods.reorganize(sort_method); + sort::SortOption { directories_first: self.directories_first, case_sensitive: self.case_sensitive, reverse: self.reverse, - sort_method, + sort_methods, } } } Только в b/src/config/default: tab.rs diff -ur a/src/config/keymap/keymapping.rs b/src/config/keymap/keymapping.rs --- a/src/config/keymap/keymapping.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/keymap/keymapping.rs 2021-08-07 11:25:14.708807426 +0300 @@ -1,6 +1,7 @@ use serde_derive::Deserialize; use std::collections::{hash_map::Entry, HashMap}; +use std::str::FromStr; #[cfg(feature = "mouse")] use termion::event::MouseEvent; @@ -27,7 +28,7 @@ fn flatten(self) -> AppKeyMapping { let mut keymaps = AppKeyMapping::new(); for m in self.mapcommand { - match KeyCommand::parse_command(m.command.as_str()) { + match KeyCommand::from_str(m.command.as_str()) { Ok(command) => { let events: Vec = m .keys @@ -167,7 +168,7 @@ let keys = [Event::Key(Key::BackTab)]; insert_keycommand(&mut m, cmd, &keys)?; - let cmd = KeyCommand::OpenFileWith; + let cmd = KeyCommand::OpenFileWith(None); let keys = [Event::Key(Key::Char('r'))]; insert_keycommand(&mut m, cmd, &keys)?; @@ -223,6 +224,10 @@ let keys = [Event::Key(Key::Char(';'))]; insert_keycommand(&mut m, cmd, &keys)?; + let cmd = KeyCommand::CommandLine("".to_string(), "".to_string()); + let keys = [Event::Key(Key::Char(':'))]; + insert_keycommand(&mut m, cmd, &keys)?; + let cmd = KeyCommand::CommandLine("mkdir ".to_string(), "".to_string()); let keys = [Event::Key(Key::Char('m')), Event::Key(Key::Char('k'))]; insert_keycommand(&mut m, cmd, &keys)?; diff -ur a/src/config/keymap/keyparse.rs b/src/config/keymap/keyparse.rs --- a/src/config/keymap/keyparse.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/keymap/keyparse.rs 2021-08-07 11:25:14.708807426 +0300 @@ -18,10 +18,10 @@ let key = match s { "backspace" => Some(Key::Backspace), "backtab" => Some(Key::BackTab), - "left" => Some(Key::Left), - "right" => Some(Key::Right), - "up" => Some(Key::Up), - "down" => Some(Key::Down), + "arrow_left" => Some(Key::Left), + "arrow_right" => Some(Key::Right), + "arrow_up" => Some(Key::Up), + "arrow_down" => Some(Key::Down), "home" => Some(Key::Home), "end" => Some(Key::End), "page_up" => Some(Key::PageUp), diff -ur a/src/config/mimetype/entry.rs b/src/config/mimetype/entry.rs --- a/src/config/mimetype/entry.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/mimetype/entry.rs 2021-08-07 11:25:14.708807426 +0300 @@ -3,7 +3,32 @@ use std::io::Read; use std::process; -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] +pub struct AppList { + #[serde(default, rename = "inherit")] + _inherit: String, + #[serde(default, rename = "app_list")] + _app_list: Vec, +} + +impl AppList { + pub fn new(_inherit: String, _app_list: Vec) -> Self { + Self { + _inherit, + _app_list, + } + } + + pub fn parent(&self) -> &str { + self._inherit.as_str() + } + + pub fn app_list(&self) -> &[AppMimetypeEntry] { + &self._app_list.as_slice() + } +} + +#[derive(Clone, Debug, Deserialize)] pub struct AppMimetypeEntry { #[serde(rename = "command")] _command: String, diff -ur a/src/config/mimetype/mod.rs b/src/config/mimetype/mod.rs --- a/src/config/mimetype/mod.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/mimetype/mod.rs 2021-08-07 11:25:14.708807426 +0300 @@ -1,5 +1,5 @@ mod entry; mod registry; -pub use self::entry::AppMimetypeEntry; +pub use self::entry::{AppList, AppMimetypeEntry}; pub use self::registry::AppMimetypeRegistry; diff -ur a/src/config/mimetype/registry.rs b/src/config/mimetype/registry.rs --- a/src/config/mimetype/registry.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/mimetype/registry.rs 2021-08-07 11:25:14.708807426 +0300 @@ -1,39 +1,69 @@ use serde_derive::Deserialize; use std::collections::HashMap; -use super::AppMimetypeEntry; -use crate::config::{parse_config_file, ConfigStructure}; +use super::{AppList, AppMimetypeEntry}; +use crate::config::{parse_to_config_file, ConfigStructure, Flattenable}; -pub type MimetypeRegistry = HashMap>; +pub type MimetypeRegistry = HashMap; #[derive(Debug, Deserialize)] +pub struct RawAppMimetypeRegistry { + #[serde(default, rename = "class")] + pub _class: HashMap>, + #[serde(default, rename = "extension")] + pub _extension: MimetypeRegistry, +} + +impl Flattenable for RawAppMimetypeRegistry { + fn flatten(self) -> AppMimetypeRegistry { + let mut registry = MimetypeRegistry::new(); + + for (ext, app_list) in self._extension { + let class = app_list.parent(); + let mut combined_app_list: Vec = self + ._class + .get(class) + .map(|v| (*v).clone()) + .unwrap_or_default(); + combined_app_list.extend_from_slice(app_list.app_list()); + let combined_app_list = AppList::new(class.to_string(), combined_app_list); + registry.insert(ext, combined_app_list); + } + + AppMimetypeRegistry { + _extension: registry, + } + } +} + +#[derive(Debug)] pub struct AppMimetypeRegistry { - #[serde(default, skip)] - empty_vec: Vec, - #[serde(default)] - pub extension: MimetypeRegistry, + // pub _class: HashMap>, + pub _extension: MimetypeRegistry, } +pub const EMPTY_ARR: [AppMimetypeEntry; 0] = []; + impl AppMimetypeRegistry { - pub fn get_entries_for_ext(&self, extension: &str) -> &[AppMimetypeEntry] { - match self.extension.get(extension) { - Some(s) => s, - None => &self.empty_vec, + pub fn app_list_for_ext(&self, extension: &str) -> &[AppMimetypeEntry] { + match self._extension.get(extension) { + Some(s) => s.app_list(), + None => &EMPTY_ARR, } } } impl ConfigStructure for AppMimetypeRegistry { fn get_config(file_name: &str) -> Self { - parse_config_file::(file_name).unwrap_or_else(Self::default) + parse_to_config_file::(file_name) + .unwrap_or_else(Self::default) } } impl std::default::Default for AppMimetypeRegistry { fn default() -> Self { Self { - empty_vec: Vec::new(), - extension: MimetypeRegistry::new(), + _extension: MimetypeRegistry::new(), } } } diff -ur a/src/config/mod.rs b/src/config/mod.rs --- a/src/config/mod.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/mod.rs 2021-08-07 11:25:14.708807426 +0300 @@ -61,26 +61,3 @@ }; Some(config.flatten()) } - -// parses a config file into its appropriate format -fn parse_config_file(filename: &str) -> Option -where - T: DeserializeOwned, -{ - let file_path = search_directories(filename, &CONFIG_HIERARCHY)?; - let file_contents = match fs::read_to_string(&file_path) { - Ok(content) => content, - Err(e) => { - eprintln!("Error reading {} file: {}", filename, e); - return None; - } - }; - - match toml::from_str::(&file_contents) { - Ok(config) => Some(config), - Err(e) => { - eprintln!("Error parsing {} file: {}", filename, e); - None - } - } -} diff -ur a/src/config/theme/style.rs b/src/config/theme/style.rs --- a/src/config/theme/style.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/config/theme/style.rs 2021-08-07 11:25:14.708807426 +0300 @@ -1,9 +1,11 @@ +use colors_transform::{Color, Rgb}; + use serde_derive::Deserialize; -use tui::style::{Color, Modifier}; +use tui::style; -const fn default_color() -> Color { - Color::Reset +const fn default_color() -> style::Color { + style::Color::Reset } #[derive(Clone, Debug, Deserialize)] @@ -25,39 +27,49 @@ let bg = Self::str_to_color(self.bg.as_str()); let fg = Self::str_to_color(self.fg.as_str()); - let mut modifier = Modifier::empty(); + let mut modifier = style::Modifier::empty(); if self.bold { - modifier.insert(Modifier::BOLD); + modifier.insert(style::Modifier::BOLD); } if self.underline { - modifier.insert(Modifier::UNDERLINED); + modifier.insert(style::Modifier::UNDERLINED); } if self.invert { - modifier.insert(Modifier::REVERSED); + modifier.insert(style::Modifier::REVERSED); } AppStyle::default().set_fg(fg).set_bg(bg).insert(modifier) } - pub fn str_to_color(s: &str) -> Color { + pub fn str_to_color(s: &str) -> style::Color { match s { - "black" => Color::Black, - "red" => Color::Red, - "blue" => Color::Blue, - "green" => Color::Green, - "yellow" => Color::Yellow, - "magenta" => Color::Magenta, - "cyan" => Color::Cyan, - "white" => Color::White, - "gray" => Color::Gray, - "dark_gray" => Color::DarkGray, - "light_red" => Color::LightRed, - "light_green" => Color::LightGreen, - "light_yellow" => Color::LightYellow, - "light_blue" => Color::LightBlue, - "light_magenta" => Color::LightMagenta, - "light_cyan" => Color::LightCyan, - _ => Color::Reset, + "black" => style::Color::Black, + "red" => style::Color::Red, + "green" => style::Color::Green, + "yellow" => style::Color::Yellow, + "blue" => style::Color::Blue, + "magenta" => style::Color::Magenta, + "cyan" => style::Color::Cyan, + "gray" => style::Color::Gray, + "dark_gray" => style::Color::DarkGray, + "light_red" => style::Color::LightRed, + "light_green" => style::Color::LightGreen, + "light_yellow" => style::Color::LightYellow, + "light_blue" => style::Color::LightBlue, + "light_magenta" => style::Color::LightMagenta, + "light_cyan" => style::Color::LightCyan, + "white" => style::Color::White, + "reset" => style::Color::Reset, + s if s.is_empty() => style::Color::Reset, + s => match s.parse::() { + Ok(rgb) => { + let r = rgb.get_red() as u8; + let g = rgb.get_green() as u8; + let b = rgb.get_blue() as u8; + style::Color::Rgb(r, g, b) + } + Err(_) => style::Color::Reset, + }, } } } @@ -76,22 +88,22 @@ #[derive(Clone, Debug)] pub struct AppStyle { - pub fg: Color, - pub bg: Color, - pub modifier: Modifier, + pub fg: style::Color, + pub bg: style::Color, + pub modifier: style::Modifier, } impl AppStyle { - pub fn set_bg(mut self, bg: Color) -> Self { + pub fn set_bg(mut self, bg: style::Color) -> Self { self.bg = bg; self } - pub fn set_fg(mut self, fg: Color) -> Self { + pub fn set_fg(mut self, fg: style::Color) -> Self { self.fg = fg; self } - pub fn insert(mut self, modifier: Modifier) -> Self { + pub fn insert(mut self, modifier: style::Modifier) -> Self { self.modifier.insert(modifier); self } @@ -102,7 +114,7 @@ Self { fg: default_color(), bg: default_color(), - modifier: Modifier::empty(), + modifier: style::Modifier::empty(), } } } diff -ur a/src/context/app_context.rs b/src/context/app_context.rs --- a/src/context/app_context.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/context/app_context.rs 2021-08-07 11:25:14.708807426 +0300 @@ -3,7 +3,7 @@ use crate::config; use crate::context::{LocalStateContext, TabContext, WorkerContext}; -use crate::util::event::{AppEvent, Events}; +use crate::event::{AppEvent, Events}; use crate::util::search::SearchPattern; pub struct AppContext { @@ -11,7 +11,7 @@ // app config config: config::AppConfig, // event loop querying - events: Events, + pub events: Events, // context related to tabs tab_context: TabContext, // context related to local file state diff -ur a/src/context/worker_context.rs b/src/context/worker_context.rs --- a/src/context/worker_context.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/context/worker_context.rs 2021-08-07 11:25:14.708807426 +0300 @@ -3,8 +3,8 @@ use std::sync::mpsc; use std::thread; +use crate::event::AppEvent; use crate::io::{IoWorkerObserver, IoWorkerProgress, IoWorkerThread}; -use crate::util::event::AppEvent; pub struct WorkerContext { // queue of IO workers Только в b/src: event.rs diff -ur a/src/fs/dirlist.rs b/src/fs/dirlist.rs --- a/src/fs/dirlist.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/fs/dirlist.rs 2021-08-07 11:25:14.712140779 +0300 @@ -108,13 +108,21 @@ Ok(()) } - pub fn selected_entries(&self) -> impl Iterator { + pub fn any_selected(&self) -> bool { + self.contents.iter().any(|e| e.is_selected()) + } + + pub fn iter_selected(&self) -> impl Iterator { self.contents.iter().filter(|entry| entry.is_selected()) } + pub fn iter_selected_mut(&mut self) -> impl Iterator { + self.contents.iter_mut().filter(|entry| entry.is_selected()) + } + pub fn get_selected_paths(&self) -> Vec { let vec: Vec = self - .selected_entries() + .iter_selected() .map(|e| e.file_path().to_path_buf()) .collect(); if !vec.is_empty() { @@ -135,6 +143,17 @@ self.get_curr_mut_(self.index?) } + /// For a given number of entries, visible in a UI, this method returns the index of the entry + /// with which the UI should start to list the entries. + /// + /// This method assures that the cursor is always in the viewport of the UI. + pub fn first_index_for_viewport(&self, viewport_height: usize) -> usize { + match self.index { + Some(index) => index / viewport_height as usize * viewport_height as usize, + None => 0, + } + } + fn get_curr_mut_(&mut self, index: usize) -> Option<&mut JoshutoDirEntry> { if index < self.contents.len() { Some(&mut self.contents[index]) diff -ur a/src/fs/entry.rs b/src/fs/entry.rs --- a/src/fs/entry.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/fs/entry.rs 2021-08-07 11:25:14.712140779 +0300 @@ -18,6 +18,7 @@ impl JoshutoDirEntry { pub fn from(direntry: &fs::DirEntry, show_icons: bool) -> std::io::Result { let path = direntry.path(); + let metadata = JoshutoMetadata::from(&path)?; let name = direntry .file_name() @@ -86,6 +87,14 @@ pub fn set_selected(&mut self, selected: bool) { self.selected = selected; } + + pub fn get_ext(&self) -> &str { + let fname = self.file_name(); + match fname.rfind('.') { + Some(pos) => &fname[pos..], + None => "", + } + } } impl std::fmt::Display for JoshutoDirEntry { diff -ur a/src/fs/metadata.rs b/src/fs/metadata.rs --- a/src/fs/metadata.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/fs/metadata.rs 2021-08-07 11:25:14.712140779 +0300 @@ -3,16 +3,23 @@ #[derive(Clone, Debug)] pub enum FileType { Directory, - Symlink(String), File, } #[derive(Clone, Debug)] +pub enum LinkType { + Normal, + Symlink(String), +} + +#[derive(Clone, Debug)] pub struct JoshutoMetadata { _len: u64, + _directory_size: Option, _modified: time::SystemTime, _permissions: fs::Permissions, _file_type: FileType, + _link_type: LinkType, #[cfg(unix)] pub uid: u32, #[cfg(unix)] @@ -26,40 +33,46 @@ #[cfg(unix)] use std::os::unix::fs::MetadataExt; - let metadata = fs::symlink_metadata(path)?; + let symlink_metadata = fs::symlink_metadata(path)?; + let metadata = fs::metadata(path)?; let _len = metadata.len(); let _modified = metadata.modified()?; let _permissions = metadata.permissions(); - let file_type = metadata.file_type(); - - let file_type = if file_type.is_dir() { - FileType::Directory - } else if file_type.is_symlink() { - let mut link = "".to_string(); - - if let Ok(path) = fs::read_link(path) { - if let Some(s) = path.to_str() { - link = s.to_string(); + let (_file_type, _directory_size) = if metadata.file_type().is_dir() { + let _directory_size = fs::read_dir(path).map(|s| s.count()).ok(); + (FileType::Directory, _directory_size) + } else { + (FileType::File, None) + }; + let _link_type = match symlink_metadata.file_type().is_symlink() { + true => { + let mut link = "".to_string(); + + if let Ok(path) = fs::read_link(path) { + if let Some(s) = path.to_str() { + link = s.to_string(); + } } + LinkType::Symlink(link) } - FileType::Symlink(link) - } else { - FileType::File + false => LinkType::Normal, }; #[cfg(unix)] - let uid = metadata.uid(); + let uid = symlink_metadata.uid(); #[cfg(unix)] - let gid = metadata.gid(); + let gid = symlink_metadata.gid(); #[cfg(unix)] - let mode = metadata.mode(); + let mode = symlink_metadata.mode(); Ok(Self { _len, + _directory_size, _modified, _permissions, - _file_type: file_type, + _file_type, + _link_type, #[cfg(unix)] uid, #[cfg(unix)] @@ -73,6 +86,10 @@ self._len } + pub fn directory_size(&self) -> Option { + self._directory_size + } + pub fn modified(&self) -> time::SystemTime { self._modified } @@ -88,4 +105,8 @@ pub fn file_type(&self) -> &FileType { &self._file_type } + + pub fn link_type(&self) -> &LinkType { + &self._link_type + } } diff -ur a/src/fs/mod.rs b/src/fs/mod.rs --- a/src/fs/mod.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/fs/mod.rs 2021-08-07 11:25:14.712140779 +0300 @@ -4,4 +4,4 @@ pub use self::dirlist::JoshutoDirList; pub use self::entry::JoshutoDirEntry; -pub use self::metadata::{FileType, JoshutoMetadata}; +pub use self::metadata::{FileType, JoshutoMetadata, LinkType}; diff -ur a/src/io/io_observer.rs b/src/io/io_observer.rs --- a/src/io/io_observer.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/io/io_observer.rs 2021-08-07 11:25:14.712140779 +0300 @@ -34,18 +34,20 @@ match self.progress.as_ref() { None => {} Some(progress) => { - let size_str = format::file_size_to_string(progress.bytes_processed()); let op_str = match progress.kind() { FileOp::Cut => "Moving", FileOp::Copy => "Copying", }; + let processed_size = format::file_size_to_string(progress.bytes_processed()); + let total_size = format::file_size_to_string(progress.total_bytes()); let msg = format!( - "{} ({}/{}) {} completed", + "{} ({}/{}) ({}/{}) completed", op_str, - progress.completed() + 1, - progress.len(), - size_str + progress.files_processed() + 1, + progress.total_files(), + processed_size, + total_size, ); self.msg = msg; } diff -ur a/src/io/io_worker.rs b/src/io/io_worker.rs --- a/src/io/io_worker.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/io/io_worker.rs 2021-08-07 11:25:14.712140779 +0300 @@ -12,21 +12,12 @@ Copy, } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub struct IoWorkerOptions { pub overwrite: bool, pub skip_exist: bool, } -impl std::default::Default for IoWorkerOptions { - fn default() -> Self { - Self { - overwrite: false, - skip_exist: false, - } - } -} - impl std::fmt::Display for IoWorkerOptions { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( @@ -40,18 +31,26 @@ #[derive(Clone, Debug)] pub struct IoWorkerProgress { _kind: FileOp, - _completed: usize, - _len: usize, + _files_processed: usize, + _total_files: usize, _bytes_processed: u64, + _total_bytes: u64, } impl IoWorkerProgress { - pub fn new(_kind: FileOp, _completed: usize, _len: usize, _bytes_processed: u64) -> Self { + pub fn new( + _kind: FileOp, + _files_processed: usize, + _total_files: usize, + _bytes_processed: u64, + _total_bytes: u64, + ) -> Self { Self { _kind, - _completed, - _len, + _files_processed, + _total_files, _bytes_processed, + _total_bytes, } } @@ -59,16 +58,16 @@ self._kind } - pub fn completed(&self) -> usize { - self._completed + pub fn files_processed(&self) -> usize { + self._files_processed } - pub fn increment_completed(&mut self) { - self._completed += 1; + pub fn set_files_processed(&mut self, files_processed: usize) { + self._files_processed = files_processed; } - pub fn len(&self) -> usize { - self._len + pub fn total_files(&self) -> usize { + self._total_files } pub fn bytes_processed(&self) -> u64 { @@ -78,6 +77,10 @@ pub fn set_bytes_processed(&mut self, _bytes_processed: u64) { self._bytes_processed = _bytes_processed; } + + pub fn total_bytes(&self) -> u64 { + self._total_bytes + } } #[derive(Debug)] @@ -107,89 +110,92 @@ self._kind } - pub fn start(&self, tx: mpsc::Sender) -> std::io::Result { + pub fn start(&self, tx: mpsc::Sender) -> io::Result { match self.kind() { FileOp::Cut => self.paste_cut(tx), FileOp::Copy => self.paste_copy(tx), } } - fn query_number_of_items(&self) -> io::Result { + fn query_number_of_items(&self) -> io::Result<(usize, u64)> { + let mut total_bytes = 0; + let mut total_files = 0; + let mut dirs: VecDeque = VecDeque::new(); for path in self.paths.iter() { let metadata = path.symlink_metadata()?; if metadata.is_dir() { dirs.push_back(path.clone()); + } else { + let metadata = path.symlink_metadata()?; + total_bytes += metadata.len(); + total_files += 1; } } - let mut total = self.paths.len() - dirs.len(); - while let Some(dir) = dirs.pop_front() { for entry in fs::read_dir(dir)? { let path = entry?.path(); if path.is_dir() { dirs.push_back(path); } else { - total += 1; + let metadata = path.symlink_metadata()?; + total_bytes += metadata.len(); + total_files += 1; } } } - Ok(total) + Ok((total_files, total_bytes)) } - fn paste_copy(&self, tx: mpsc::Sender) -> std::io::Result { - let num_items = self.query_number_of_items()?; - let mut progress = IoWorkerProgress::new(self.kind(), 0, num_items, 0); + fn paste_copy(&self, tx: mpsc::Sender) -> io::Result { + let (total_files, total_bytes) = self.query_number_of_items()?; + let mut progress = IoWorkerProgress::new(self.kind(), 0, total_files, 0, total_bytes); for path in self.paths.iter() { let _ = tx.send(progress.clone()); recursive_copy( + self.options, path.as_path(), self.dest.as_path(), tx.clone(), &mut progress, )?; } - Ok(IoWorkerProgress::new( - self.kind(), - self.paths.len(), - self.paths.len(), - progress.bytes_processed(), - )) + Ok(progress) } - fn paste_cut(&self, tx: mpsc::Sender) -> std::io::Result { - let num_items = self.query_number_of_items()?; - let mut progress = IoWorkerProgress::new(self.kind(), 0, num_items, 0); + fn paste_cut(&self, tx: mpsc::Sender) -> io::Result { + let (total_files, total_bytes) = self.query_number_of_items()?; + let mut progress = IoWorkerProgress::new(self.kind(), 0, total_files, 0, total_bytes); + for path in self.paths.iter() { let _ = tx.send(progress.clone()); recursive_cut( + self.options, path.as_path(), self.dest.as_path(), tx.clone(), &mut progress, )?; } - Ok(IoWorkerProgress::new( - self.kind(), - self.paths.len(), - self.paths.len(), - progress.bytes_processed(), - )) + Ok(progress) } } pub fn recursive_copy( + options: IoWorkerOptions, src: &path::Path, dest: &path::Path, tx: mpsc::Sender, progress: &mut IoWorkerProgress, -) -> std::io::Result<()> { +) -> io::Result<()> { let mut dest_buf = dest.to_path_buf(); if let Some(s) = src.file_name() { dest_buf.push(s); } - rename_filename_conflict(&mut dest_buf); + if !options.overwrite { + rename_filename_conflict(&mut dest_buf); + } let file_type = fs::symlink_metadata(src)?.file_type(); if file_type.is_dir() { fs::create_dir(dest_buf.as_path())?; @@ -197,6 +203,7 @@ let entry = entry?; let entry_path = entry.path(); recursive_copy( + options, entry_path.as_path(), dest_buf.as_path(), tx.clone(), @@ -208,12 +215,12 @@ } else if file_type.is_file() { let bytes_processed = progress.bytes_processed() + fs::copy(src, dest_buf)?; progress.set_bytes_processed(bytes_processed); - progress.increment_completed(); + progress.set_files_processed(progress.files_processed() + 1); Ok(()) } else if file_type.is_symlink() { let link_path = fs::read_link(src)?; std::os::unix::fs::symlink(link_path, dest_buf)?; - progress.increment_completed(); + progress.set_files_processed(progress.files_processed() + 1); Ok(()) } else { Ok(()) @@ -221,29 +228,36 @@ } pub fn recursive_cut( + options: IoWorkerOptions, src: &path::Path, dest: &path::Path, tx: mpsc::Sender, progress: &mut IoWorkerProgress, -) -> std::io::Result<()> { +) -> io::Result<()> { let mut dest_buf = dest.to_path_buf(); if let Some(s) = src.file_name() { dest_buf.push(s); } - rename_filename_conflict(&mut dest_buf); + if !options.overwrite { + rename_filename_conflict(&mut dest_buf); + } let metadata = fs::symlink_metadata(src)?; let file_type = metadata.file_type(); - if file_type.is_dir() { - match fs::rename(src, dest_buf.as_path()) { - Ok(_) => { - let processed = progress.bytes_processed() + metadata.len(); - progress.set_bytes_processed(processed); - } - Err(_) => { + + match fs::rename(src, dest_buf.as_path()) { + Ok(_) => { + let bytes_processed = progress.bytes_processed() + metadata.len(); + progress.set_bytes_processed(bytes_processed); + progress.set_files_processed(progress.files_processed() + 1); + Ok(()) + } + Err(e) if e.kind() == io::ErrorKind::Other => { + if file_type.is_dir() { fs::create_dir(dest_buf.as_path())?; for entry in fs::read_dir(src)? { let entry_path = entry?.path(); recursive_cut( + options, entry_path.as_path(), dest_buf.as_path(), tx.clone(), @@ -251,23 +265,21 @@ )?; } fs::remove_dir(src)?; + } else if file_type.is_symlink() { + let link_path = fs::read_link(src)?; + std::os::unix::fs::symlink(link_path, dest_buf)?; + fs::remove_file(src)?; + let processed = progress.bytes_processed() + metadata.len(); + progress.set_bytes_processed(processed); + progress.set_files_processed(progress.files_processed() + 1); + } else { + let processed = progress.bytes_processed() + fs::copy(src, dest_buf.as_path())?; + fs::remove_file(src)?; + progress.set_bytes_processed(processed); + progress.set_files_processed(progress.files_processed() + 1); } + Ok(()) } - } else if file_type.is_file() { - if fs::rename(src, dest_buf.as_path()).is_err() { - fs::copy(src, dest_buf.as_path())?; - fs::remove_file(src)?; - let processed = progress.bytes_processed() + metadata.len(); - progress.set_bytes_processed(processed); - } - progress.increment_completed(); - } else if file_type.is_symlink() { - let link_path = fs::read_link(src)?; - std::os::unix::fs::symlink(link_path, dest_buf)?; - fs::remove_file(src)?; - let processed = progress.bytes_processed() + metadata.len(); - progress.set_bytes_processed(processed); - progress.increment_completed(); + e => e, } - Ok(()) } diff -ur a/src/main.rs b/src/main.rs --- a/src/main.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/main.rs 2021-08-07 11:25:14.712140779 +0300 @@ -2,9 +2,11 @@ mod config; mod context; mod error; +mod event; mod fs; mod history; mod io; +mod preview; mod run; mod tab; mod ui; @@ -34,16 +36,32 @@ lazy_static! { // dynamically builds the config hierarchy static ref CONFIG_HIERARCHY: Vec = { - let mut temp = vec![]; - match xdg::BaseDirectories::with_prefix(PROGRAM_NAME) { - Ok(dirs) => temp.push(dirs.get_config_home()), - Err(e) => eprintln!("{}", e), - }; + let mut config_dirs = vec![]; + + if let Ok(p) = std::env::var("JOSHUTO_CONFIG_HOME") { + let p = PathBuf::from(p); + if p.is_dir() { + config_dirs.push(p); + } + } + + if let Ok(dirs) = xdg::BaseDirectories::with_prefix(PROGRAM_NAME) { + config_dirs.push(dirs.get_config_home()); + } + + if let Ok(p) = std::env::var("HOME") { + let mut p = PathBuf::from(p); + p.push(".config/joshuto"); + if p.is_dir() { + config_dirs.push(p); + } + } + // adds the default config files to the config hierarchy if running through cargo if cfg!(debug_assertions) { - temp.push(PathBuf::from("./config")); + config_dirs.push(PathBuf::from("./config")); } - temp + config_dirs }; static ref THEME_T: AppTheme = AppTheme::get_config(THEME_FILE); static ref MIMETYPE_T: AppMimetypeRegistry = AppMimetypeRegistry::get_config(MIMETYPE_FILE); @@ -71,21 +89,17 @@ return Ok(()); } if let Some(p) = args.path.as_ref() { - match std::env::set_current_dir(p.as_path()) { - Ok(_) => {} - Err(e) => { - eprintln!("{}", e); - process::exit(1); - } + if let Err(e) = std::env::set_current_dir(p.as_path()) { + eprintln!("{}", e); + process::exit(1); } } let config = AppConfig::get_config(CONFIG_FILE); let keymap = AppKeyMapping::get_config(KEYMAP_FILE); - let mut context = AppContext::new(config); - { + let mut context = AppContext::new(config); let mut backend: ui::TuiBackend = ui::TuiBackend::new()?; run(&mut backend, &mut context, keymap)?; } @@ -108,11 +122,8 @@ fn main() { let args = Args::from_args(); - match run_joshuto(args) { - Ok(_) => {} - Err(e) => { - eprintln!("{}", e.to_string()); - process::exit(1); - } + if let Err(e) = run_joshuto(args) { + eprintln!("{}", e.to_string()); + process::exit(1); } } Только в b/src: preview diff -ur a/src/run.rs b/src/run.rs --- a/src/run.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/run.rs 2021-08-07 11:25:14.712140779 +0300 @@ -3,12 +3,12 @@ use crate::commands::{AppExecute, CommandKeybind, KeyCommand}; use crate::config::AppKeyMapping; use crate::context::AppContext; +use crate::event::AppEvent; +use crate::preview::preview_default; use crate::tab::JoshutoTab; use crate::ui; use crate::ui::views::{TuiCommandMenu, TuiView}; -use crate::util::event::AppEvent; use crate::util::input; -use crate::util::load_child::LoadChild; use crate::util::to_string::ToString; pub fn run( @@ -23,7 +23,7 @@ context.tab_context_mut().push_tab(tab); // trigger a preview of child - LoadChild::load_child(context)?; + preview_default::load_preview(context, backend); } while !context.exit { @@ -40,6 +40,7 @@ match event { AppEvent::Termion(Event::Mouse(event)) => { input::process_mouse(event, context, backend); + preview_default::load_preview(context, backend); } AppEvent::Termion(key) => { if !context.message_queue_ref().is_empty() { @@ -82,6 +83,7 @@ }, } context.flush_event(); + preview_default::load_preview(context, backend); } event => input::process_noninteractive(event, context), } diff -ur a/src/tab.rs b/src/tab.rs --- a/src/tab.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/tab.rs 2021-08-07 11:25:14.712140779 +0300 @@ -4,6 +4,13 @@ use crate::history::{DirectoryHistory, JoshutoHistory}; use crate::util::display::DisplayOption; +#[derive(Clone, Copy, Debug)] +pub enum TabHomePage { + Inherit, + Home, + Root, +} + pub struct JoshutoTab { history: JoshutoHistory, _cwd: path::PathBuf, diff -ur a/src/ui/views/tui_command_menu.rs b/src/ui/views/tui_command_menu.rs --- a/src/ui/views/tui_command_menu.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/views/tui_command_menu.rs 2021-08-07 11:25:14.712140779 +0300 @@ -7,10 +7,10 @@ use crate::commands::{CommandKeybind, KeyCommand}; use crate::config::AppKeyMapping; use crate::context::AppContext; +use crate::event::AppEvent; use crate::ui::views::TuiView; use crate::ui::widgets::TuiMenu; use crate::ui::TuiBackend; -use crate::util::event::AppEvent; use crate::util::input; use crate::util::to_string::ToString; @@ -36,11 +36,11 @@ loop { let _ = terminal.draw(|frame| { - let f_size: Rect = frame.size(); + let area = frame.size(); { let view = TuiView::new(&context); - frame.render_widget(view, f_size); + frame.render_widget(view, area); } { @@ -54,21 +54,27 @@ let display_str: Vec<&str> = display_vec.iter().map(|v| v.as_str()).collect(); let display_str_len = display_str.len(); - let y = if (f_size.height as usize) + let y = if (area.height as usize) < display_str_len + BORDER_HEIGHT + BOTTOM_MARGIN { 0 } else { - f_size.height + area.height - (BORDER_HEIGHT + BOTTOM_MARGIN) as u16 - display_str_len as u16 }; + let menu_height = if display_str_len + BORDER_HEIGHT > area.height as usize { + area.height + } else { + (display_str_len + BORDER_HEIGHT) as u16 + }; + let menu_rect = Rect { x: 0, y, - width: f_size.width, - height: (display_str_len + BORDER_HEIGHT) as u16, + width: area.width, + height: menu_height, }; frame.render_widget(Clear, menu_rect); diff -ur a/src/ui/views/tui_folder_view.rs b/src/ui/views/tui_folder_view.rs --- a/src/ui/views/tui_folder_view.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/views/tui_folder_view.rs 2021-08-07 11:25:14.712140779 +0300 @@ -1,6 +1,7 @@ use tui::buffer::Buffer; use tui::layout::{Constraint, Direction, Layout, Rect}; use tui::style::{Color, Style}; +use tui::symbols::line::{HORIZONTAL_DOWN, HORIZONTAL_UP}; use tui::text::Span; use tui::widgets::{Block, Borders, Paragraph, Widget, Wrap}; @@ -48,6 +49,7 @@ height: area.height - 2, ..area }; + let block = Block::default().borders(Borders::ALL); let inner = block.inner(area); block.render(area, buf); @@ -57,6 +59,25 @@ .constraints(constraints.as_ref()) .split(inner); + // Render inner borders properly. + { + let top = area.top(); + let bottom = area.bottom() - 1; + let left = layout_rect[1].left() - 1; + let right = layout_rect[2].left(); + let intersections = Intersections { + top, + bottom, + left, + right, + }; + + intersections.render_left(buf); + if child_list.as_ref().is_some() { + intersections.render_right(buf); + } + } + let block = Block::default().borders(Borders::RIGHT); let inner1 = block.inner(layout_rect[0]); block.render(layout_rect[0], buf); @@ -160,3 +181,24 @@ } } } + +struct Intersections { + top: u16, + bottom: u16, + left: u16, + right: u16, +} + +impl Intersections { + fn render_left(&self, buf: &mut Buffer) { + buf.get_mut(self.left, self.top).set_symbol(HORIZONTAL_DOWN); + buf.get_mut(self.left, self.bottom) + .set_symbol(HORIZONTAL_UP); + } + fn render_right(&self, buf: &mut Buffer) { + buf.get_mut(self.right, self.top) + .set_symbol(HORIZONTAL_DOWN); + buf.get_mut(self.right, self.bottom) + .set_symbol(HORIZONTAL_UP); + } +} diff -ur a/src/ui/views/tui_textfield.rs b/src/ui/views/tui_textfield.rs --- a/src/ui/views/tui_textfield.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/views/tui_textfield.rs 2021-08-07 11:25:14.715474131 +0300 @@ -7,10 +7,10 @@ use unicode_width::UnicodeWidthStr; use crate::context::AppContext; +use crate::event::AppEvent; use crate::ui::views::TuiView; use crate::ui::widgets::{TuiMenu, TuiMultilineText}; use crate::ui::TuiBackend; -use crate::util::event::AppEvent; use crate::util::input; struct CompletionTracker { diff -ur a/src/ui/views/tui_worker_view.rs b/src/ui/views/tui_worker_view.rs --- a/src/ui/views/tui_worker_view.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/views/tui_worker_view.rs 2021-08-07 11:25:14.715474131 +0300 @@ -3,9 +3,9 @@ use tui::layout::Rect; use crate::context::AppContext; +use crate::event::AppEvent; use crate::ui::widgets::{TuiTopBar, TuiWorker}; use crate::ui::TuiBackend; -use crate::util::event::AppEvent; use crate::util::input; pub struct TuiWorkerView {} @@ -43,11 +43,8 @@ if let Ok(event) = context.poll_event() { match event { AppEvent::Termion(event) => { - match event { - Event::Key(Key::Esc) => { - break; - } - _ => {} + if let Event::Key(Key::Esc) = event { + break; } context.flush_event(); } diff -ur a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs --- a/src/ui/widgets/mod.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/widgets/mod.rs 2021-08-07 11:25:14.715474131 +0300 @@ -9,7 +9,7 @@ mod tui_worker; pub use self::tui_dirlist::TuiDirList; -pub use self::tui_dirlist_detailed::TuiDirListDetailed; +pub use self::tui_dirlist_detailed::{trim_file_label, TuiDirListDetailed}; pub use self::tui_footer::TuiFooter; pub use self::tui_menu::TuiMenu; pub use self::tui_prompt::TuiPrompt; diff -ur a/src/ui/widgets/tui_dirlist_detailed.rs b/src/ui/widgets/tui_dirlist_detailed.rs --- a/src/ui/widgets/tui_dirlist_detailed.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/widgets/tui_dirlist_detailed.rs 2021-08-07 11:25:14.715474131 +0300 @@ -2,13 +2,14 @@ use tui::layout::Rect; use tui::style::{Color, Modifier, Style}; use tui::widgets::Widget; -use unicode_width::UnicodeWidthStr; -use crate::fs::{FileType, JoshutoDirEntry, JoshutoDirList}; +use crate::fs::{FileType, JoshutoDirEntry, JoshutoDirList, LinkType}; use crate::util::format; +use crate::util::string::UnicodeTruncate; use crate::util::style; +use unicode_width::UnicodeWidthStr; -const FILE_SIZE_WIDTH: usize = 8; +const MIN_LEFT_LABEL_WIDTH: i32 = 15; const ELLIPSIS: &str = "…"; @@ -40,7 +41,7 @@ }; let drawing_width = area.width as usize; - let skip_dist = curr_index / area.height as usize * area.height as usize; + let skip_dist = self.dirlist.first_index_for_viewport(area.height as usize); // draw every entry self.dirlist @@ -79,77 +80,223 @@ (x, y): (u16, u16), drawing_width: usize, ) { - let name = entry.label(); - let name_width = name.width(); + let size_string = match entry.metadata.file_type() { + FileType::Directory => entry + .metadata + .directory_size() + .map(|n| n.to_string()) + .unwrap_or("".to_string()), + FileType::File => format::file_size_to_string(entry.metadata.len()), + }; + let symlink_string = match entry.metadata.link_type() { + LinkType::Normal => "", + LinkType::Symlink(_) => "-> ", + }; + let left_label_original = entry.label(); + let right_label_original = format!(" {}{} ", symlink_string, size_string); + + let (left_label, right_label) = factor_labels_for_entry( + left_label_original, + right_label_original.as_str(), + drawing_width, + ); + + let right_width = right_label.width(); + buf.set_stringn(x, y, left_label, drawing_width, style); + buf.set_stringn( + x + drawing_width as u16 - right_width as u16, + y, + right_label, + drawing_width, + style, + ); +} - match entry.metadata.file_type() { - FileType::Directory => { - // print filename - buf.set_stringn(x, y, name, drawing_width, style); - if name_width > drawing_width { - buf.set_string(x + drawing_width as u16 - 1, y, ELLIPSIS, style); - } - } - FileType::Symlink(_) => { - // print filename - buf.set_stringn(x, y, name, drawing_width, style); - buf.set_string(x + drawing_width as u16 - 4, y, "->", style); - if name_width >= drawing_width - 4 { - buf.set_string(x + drawing_width as u16 - 1, y, ELLIPSIS, style); - } +fn factor_labels_for_entry<'a>( + left_label_original: &'a str, + right_label_original: &'a str, + drawing_width: usize, +) -> (String, &'a str) { + let left_label_original_width = left_label_original.width(); + let right_label_original_width = right_label_original.width(); + + let left_width_remainder = drawing_width as i32 - right_label_original_width as i32; + let width_remainder = left_width_remainder as i32 - left_label_original_width as i32; + + if drawing_width == 0 { + ("".to_string(), "") + } else if width_remainder >= 0 { + (left_label_original.to_string(), right_label_original) + } else { + if left_width_remainder < MIN_LEFT_LABEL_WIDTH { + ( + if left_label_original.width() as i32 <= left_width_remainder { + trim_file_label(left_label_original, drawing_width) + } else { + left_label_original.to_string() + }, + "", + ) + } else { + ( + trim_file_label(left_label_original, left_width_remainder as usize), + right_label_original, + ) } - FileType::File => { - if drawing_width < FILE_SIZE_WIDTH { - return; - } - let file_drawing_width = drawing_width - FILE_SIZE_WIDTH; + } +} - let (stem, extension) = match name.rfind('.') { - None => (name, ""), - Some(i) => name.split_at(i), - }; - if stem.is_empty() { - let ext_width = extension.width(); - buf.set_stringn(x, y, extension, file_drawing_width, style); - if ext_width > drawing_width { - buf.set_string(x + drawing_width as u16 - 1, y, ELLIPSIS, style); - } - } else if extension.is_empty() { - let stem_width = stem.width(); - buf.set_stringn(x, y, stem, file_drawing_width, style); - if stem_width > file_drawing_width { - buf.set_string(x + drawing_width as u16 - 1, y, ELLIPSIS, style); - } - } else { - let stem_width = stem.width(); - let ext_width = extension.width(); - buf.set_stringn(x, y, stem, file_drawing_width, style); - if stem_width + ext_width > file_drawing_width { - let ext_start_idx = if file_drawing_width < ext_width { - 0 - } else { - (file_drawing_width - ext_width) as u16 - }; - buf.set_string(x + ext_start_idx, y, extension, style); - let ext_start_idx = if ext_start_idx > 0 { - ext_start_idx - 1 - } else { - 0 - }; - buf.set_string(x + ext_start_idx, y, ELLIPSIS, style); - } else { - buf.set_string(x + stem_width as u16, y, extension, style); - } - } - // print file size - let file_size_string = format::file_size_to_string(entry.metadata.len()); - buf.set_string(x + file_drawing_width as u16, y, " ", style); - buf.set_string( - x + file_drawing_width as u16 + 1, - y, - file_size_string, - style, - ); +pub fn trim_file_label(name: &str, drawing_width: usize) -> String { + // pre-condition: string name is longer than width + let (stem, extension) = match name.rfind('.') { + None => (name, ""), + Some(i) => name.split_at(i), + }; + if drawing_width < 1 { + String::from("") + } else if stem.is_empty() || extension.is_empty() { + let full = format!("{}{}", stem, extension); + let mut truncated = full.trunc(drawing_width - 1); + truncated.push_str(ELLIPSIS); + truncated + } else { + let ext_width = extension.width(); + if ext_width > drawing_width { + // file ext does not fit + ELLIPSIS.to_string() + } else if ext_width == drawing_width { + extension.replacen('.', ELLIPSIS, 1).to_string() + } else { + let stem_width = drawing_width - ext_width; + let truncated_stem = stem.trunc(stem_width - 1); + format!("{}{}{}", truncated_stem, ELLIPSIS, extension) } } } + +#[cfg(test)] +mod test_factor_labels { + use super::{factor_labels_for_entry, MIN_LEFT_LABEL_WIDTH}; + + #[test] + fn both_labels_empty_if_drawing_width_zero() { + let left = "foo.ext"; + let right = "right"; + assert_eq!( + ("".to_string(), ""), + factor_labels_for_entry(left, right, 0) + ); + } + + #[test] + fn nothing_changes_if_all_labels_fit_easily() { + let left = "foo.ext"; + let right = "right"; + assert_eq!( + (left.to_string(), right), + factor_labels_for_entry(left, right, 20) + ); + } + + #[test] + fn nothing_changes_if_all_labels_just_fit() { + let left = "foo.ext"; + let right = "right"; + assert_eq!( + (left.to_string(), right), + factor_labels_for_entry(left, right, 12) + ); + } + + #[test] + fn right_label_omitted_if_left_label_would_need_to_be_shortened_below_min_left_label_width() { + let left = "foobarbazfo.ext"; + let right = "right"; + assert!(left.chars().count() as i32 == MIN_LEFT_LABEL_WIDTH); + assert_eq!( + ("foobarbazfo.ext".to_string(), ""), + factor_labels_for_entry(left, right, MIN_LEFT_LABEL_WIDTH as usize) + ); + } + + #[test] + fn right_label_is_kept_if_left_label_is_not_shortened_below_min_left_label_width() { + let left = "foobarbazfoobarbaz.ext"; + let right = "right"; + assert!(left.chars().count() as i32 > MIN_LEFT_LABEL_WIDTH + right.chars().count() as i32); + assert_eq!( + ("foobarbazf….ext".to_string(), right), + factor_labels_for_entry( + left, + right, + MIN_LEFT_LABEL_WIDTH as usize + right.chars().count() + ) + ); + } + + #[test] + // regression + fn file_name_which_is_smaller_or_equal_drawing_width_does_not_cause_right_label_to_be_omitted() + { + let left = "foooooobaaaaaaarbaaaaaaaaaz"; + let right = "right"; + assert!(left.chars().count() as i32 > MIN_LEFT_LABEL_WIDTH); + assert_eq!( + ("foooooobaaaaaaarbaaaa…".to_string(), right), + factor_labels_for_entry(left, right, left.chars().count()) + ); + } +} + +#[cfg(test)] +mod test_trim_file_label { + use super::trim_file_label; + + #[test] + fn dotfiles_get_an_ellipsis_at_the_end_if_they_dont_fit() { + let label = ".joshuto"; + assert_eq!(".jos…".to_string(), trim_file_label(label, 5)); + } + + #[test] + fn dotless_files_get_an_ellipsis_at_the_end_if_they_dont_fit() { + let label = "Desktop"; + assert_eq!("Desk…".to_string(), trim_file_label(label, 5)); + } + + #[test] + fn if_the_extension_doesnt_fit_just_an_ellipses_is_shown() { + let label = "foo.ext"; + assert_eq!("…".to_string(), trim_file_label(label, 2)); + } + + #[test] + fn if_just_the_extension_fits_its_shown_with_an_ellipsis_instead_of_a_dot() { + let left = "foo.ext"; + assert_eq!("…ext".to_string(), trim_file_label(left, 4)); + } + + #[test] + fn if_the_extension_fits_the_stem_is_truncated_with_an_appended_ellipsis_1() { + let left = "foo.ext"; + assert_eq!("….ext".to_string(), trim_file_label(left, 5)); + } + + #[test] + fn if_the_extension_fits_the_stem_is_truncated_with_an_appended_ellipsis_2() { + let left = "foo.ext"; + assert_eq!("f….ext".to_string(), trim_file_label(left, 6)); + } + + #[test] + fn if_the_name_is_truncated_after_a_full_width_character_the_ellipsis_is_shown_correctly() { + let left = "🌕🌕🌕"; + assert_eq!("🌕…".to_string(), trim_file_label(left, 4)); + } + + #[test] + fn if_the_name_is_truncated_within_a_full_width_character_the_ellipsis_is_shown_correctly() { + let left = "🌕🌕🌕"; + assert_eq!("🌕🌕…".to_string(), trim_file_label(left, 5)); + } +} diff -ur a/src/ui/widgets/tui_dirlist.rs b/src/ui/widgets/tui_dirlist.rs --- a/src/ui/widgets/tui_dirlist.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/widgets/tui_dirlist.rs 2021-08-07 11:25:14.715474131 +0300 @@ -4,11 +4,10 @@ use tui::widgets::Widget; use unicode_width::UnicodeWidthStr; -use crate::fs::{FileType, JoshutoDirEntry, JoshutoDirList}; +use crate::fs::{JoshutoDirEntry, JoshutoDirList}; +use crate::ui::widgets::trim_file_label; use crate::util::style; -const ELLIPSIS: &str = "…"; - pub struct TuiDirList<'a> { dirlist: &'a JoshutoDirList, } @@ -37,7 +36,7 @@ } let curr_index = self.dirlist.index.unwrap(); - let skip_dist = curr_index / area.height as usize * area.height as usize; + let skip_dist = self.dirlist.first_index_for_viewport(area.height as usize); let drawing_width = area.width as usize; @@ -79,54 +78,10 @@ ) { let name = entry.label(); let name_width = name.width(); - - match entry.metadata.file_type() { - FileType::Directory => { - // print filename - buf.set_stringn(x, y, name, drawing_width, style); - if name_width > drawing_width { - buf.set_string(x + drawing_width as u16 - 1, y, ELLIPSIS, style); - } - } - _ => { - let file_drawing_width = drawing_width; - let (stem, extension) = match name.rfind('.') { - None => (name, ""), - Some(i) => name.split_at(i), - }; - if stem.is_empty() { - let ext_width = extension.width(); - buf.set_stringn(x, y, extension, file_drawing_width, style); - if ext_width > drawing_width { - buf.set_string(x + drawing_width as u16 - 1, y, ELLIPSIS, style); - } - } else if extension.is_empty() { - let stem_width = stem.width(); - buf.set_stringn(x, y, stem, file_drawing_width, style); - if stem_width > file_drawing_width { - buf.set_string(x + file_drawing_width as u16 - 1, y, ELLIPSIS, style); - } - } else { - let stem_width = stem.width(); - let ext_width = extension.width(); - buf.set_stringn(x, y, stem, file_drawing_width, style); - if stem_width + ext_width > file_drawing_width { - let ext_start_idx = if file_drawing_width < ext_width { - 0 - } else { - (file_drawing_width - ext_width) as u16 - }; - buf.set_string(x + ext_start_idx, y, extension, style); - let ext_start_idx = if ext_start_idx > 0 { - ext_start_idx - 1 - } else { - 0 - }; - buf.set_string(x + ext_start_idx, y, ELLIPSIS, style); - } else { - buf.set_string(x + stem_width as u16, y, extension, style); - } - } - } - } + let label = if name_width > drawing_width { + trim_file_label(name, drawing_width) + } else { + name.to_string() + }; + buf.set_string(x, y, label, style); } diff -ur a/src/ui/widgets/tui_footer.rs b/src/ui/widgets/tui_footer.rs --- a/src/ui/widgets/tui_footer.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/widgets/tui_footer.rs 2021-08-07 11:25:14.715474131 +0300 @@ -4,7 +4,7 @@ use tui::text::{Span, Spans}; use tui::widgets::{Paragraph, Widget}; -use crate::fs::{FileType, JoshutoDirList}; +use crate::fs::{JoshutoDirList, LinkType}; use crate::util::format; use crate::util::unix; @@ -41,7 +41,7 @@ Span::raw(size_str), ]; - if let FileType::Symlink(s) = entry.metadata.file_type() { + if let LinkType::Symlink(s) = entry.metadata.link_type() { text.push(Span::styled(" -> ", mode_style)); text.push(Span::styled(s, mode_style)); } diff -ur a/src/ui/widgets/tui_menu.rs b/src/ui/widgets/tui_menu.rs --- a/src/ui/widgets/tui_menu.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/widgets/tui_menu.rs 2021-08-07 11:25:14.715474131 +0300 @@ -19,18 +19,18 @@ impl<'a> Widget for TuiMenu<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - let text_iter = self.options.iter().chain(&[" "]); let style = Style::default().fg(Color::Reset).bg(Color::Reset); - let area_x = area.x + 1; - let area_y = area.y + 1; Block::default() .style(style) .borders(Borders::TOP) .render(area, buf); - for (i, text) in text_iter.enumerate() { - buf.set_string(area_x, area_y + i as u16, text, style); + let text_iter = self.options.iter().chain(&[" "]); + let area_x = area.x + 1; + + for (y, text) in (area.y + 1..area.y + area.height).zip(text_iter) { + buf.set_string(area_x, y, text, style); } } } diff -ur a/src/ui/widgets/tui_prompt.rs b/src/ui/widgets/tui_prompt.rs --- a/src/ui/widgets/tui_prompt.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/widgets/tui_prompt.rs 2021-08-07 11:25:14.715474131 +0300 @@ -5,9 +5,9 @@ use tui::widgets::{Clear, Paragraph, Wrap}; use crate::context::AppContext; +use crate::event::AppEvent; use crate::ui::views::TuiView; use crate::ui::TuiBackend; -use crate::util::event::AppEvent; use crate::util::input; pub struct TuiPrompt<'a> { diff -ur a/src/ui/widgets/tui_topbar.rs b/src/ui/widgets/tui_topbar.rs --- a/src/ui/widgets/tui_topbar.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/widgets/tui_topbar.rs 2021-08-07 11:25:14.715474131 +0300 @@ -22,10 +22,6 @@ impl<'a> Widget for TuiTopBar<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - let username_style = Style::default() - .fg(Color::LightGreen) - .add_modifier(Modifier::BOLD); - let path_style = Style::default() .fg(Color::LightBlue) .add_modifier(Modifier::BOLD); @@ -34,12 +30,9 @@ let mut curr_path_str = self.path.to_string_lossy().into_owned(); if curr_path_str.len() > area.width as usize { - match self.path.file_name() { - Some(s) => { - curr_path_str = s.to_string_lossy().into_owned(); - ellipses = Some(Span::styled("…", path_style)); - } - None => {} + if let Some(s) = self.path.file_name() { + curr_path_str = s.to_string_lossy().into_owned(); + ellipses = Some(Span::styled("…", path_style)); } } if self @@ -54,6 +47,14 @@ } } + let username_style = if USERNAME.as_str() == "root" { + Style::default().fg(Color::Red).add_modifier(Modifier::BOLD) + } else { + Style::default() + .fg(Color::LightGreen) + .add_modifier(Modifier::BOLD) + }; + let text = match ellipses { Some(s) => Spans::from(vec![ Span::styled(USERNAME.as_str(), username_style), diff -ur a/src/ui/widgets/tui_worker.rs b/src/ui/widgets/tui_worker.rs --- a/src/ui/widgets/tui_worker.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/ui/widgets/tui_worker.rs 2021-08-07 11:25:14.715474131 +0300 @@ -5,6 +5,7 @@ use crate::context::AppContext; use crate::io::FileOp; +use crate::util::format; pub struct TuiWorker<'a> { pub context: &'a AppContext, @@ -25,19 +26,28 @@ FileOp::Copy => "Copying", FileOp::Cut => "Moving", }; + + let processed_size = format::file_size_to_string(progress.bytes_processed()); + let total_size = format::file_size_to_string(progress.total_bytes()); + let msg = format!( - "{} ({}/{}) {:?}", + "{} ({}/{}) ({}/{}) {:?}", op_str, - progress.completed() + 1, - progress.len(), + progress.files_processed() + 1, + progress.total_files(), + processed_size, + total_size, io_obs.dest_path() ); + let style = Style::default(); buf.set_stringn(0, 2, msg, area.width as usize, style); // draw a progress bar - let progress_bar_width = (progress.completed() as f32 / progress.len() as f32 + let progress_bar_width = (progress.files_processed() as f32 + / progress.total_files() as f32 * area.width as f32) as usize; + let progress_bar_space = " ".repeat(progress_bar_width); let style = Style::default().bg(Color::Blue); buf.set_stringn(0, 3, progress_bar_space, area.width as usize, style); diff -ur a/src/util/display.rs b/src/util/display.rs --- a/src/util/display.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/util/display.rs 2021-08-07 11:25:14.715474131 +0300 @@ -106,14 +106,11 @@ fn filter_hidden(result: &Result) -> bool { match result { - Err(_) => false, + Err(_) => true, Ok(entry) => { let file_name = entry.file_name(); - if let Some(file_name) = file_name.to_str() { - !file_name.starts_with('.') - } else { - false - } + let lossy_string = file_name.as_os_str().to_string_lossy(); + !lossy_string.starts_with('.') } } } Только в a/src/util: event.rs diff -ur a/src/util/input.rs b/src/util/input.rs --- a/src/util/input.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/util/input.rs 2021-08-07 11:25:14.715474131 +0300 @@ -4,10 +4,10 @@ use crate::commands::{cursor_move, parent_cursor_move, AppExecute, KeyCommand}; use crate::context::AppContext; +use crate::event::AppEvent; use crate::history::DirectoryHistory; use crate::io::{FileOp, IoWorkerProgress}; use crate::ui; -use crate::util::event::AppEvent; use crate::util::format; pub fn process_mouse(event: MouseEvent, context: &mut AppContext, backend: &mut ui::TuiBackend) { @@ -16,7 +16,13 @@ let constraints: &[Constraint; 3] = &context.config_ref().display_options_ref().default_layout; let layout_rect = Layout::default() .direction(Direction::Horizontal) - .vertical_margin(1) + .vertical_margin( + if context.config_ref().display_options_ref().show_borders() { + 2 + } else { + 1 + }, + ) .constraints(constraints.as_ref()) .split(f_size); @@ -54,28 +60,28 @@ MouseEvent::Press(MouseButton::Left, x, y) if y > layout_rect[1].y && y <= layout_rect[1].y + layout_rect[1].height => { - if x < layout_rect[1].x { - if let Some(dirlist) = context.tab_context_ref().curr_tab_ref().curr_list_ref() { - if let Some(curr_index) = dirlist.index { - let skip_dist = curr_index / layout_rect[1].height as usize - * layout_rect[1].height as usize; - - let new_index = skip_dist + (y - layout_rect[1].y - 1) as usize; - if let Err(e) = parent_cursor_move::parent_cursor_move(new_index, context) { - context.push_msg(e.to_string()); - } - } - } - } else if x < layout_rect[2].x { - if let Some(dirlist) = context.tab_context_ref().curr_tab_ref().curr_list_ref() { - if let Some(curr_index) = dirlist.index { - let skip_dist = curr_index / layout_rect[1].height as usize - * layout_rect[1].height as usize; - - let new_index = skip_dist + (y - layout_rect[1].y - 1) as usize; - if let Err(e) = cursor_move::cursor_move(new_index, context) { - context.push_msg(e.to_string()); - } + if x < layout_rect[2].x { + let (dirlist, is_parent) = if x < layout_rect[1].x { + ( + context.tab_context_ref().curr_tab_ref().parent_list_ref(), + true, + ) + } else { + ( + context.tab_context_ref().curr_tab_ref().curr_list_ref(), + false, + ) + }; + if let Some(dirlist) = dirlist { + let skip_dist = + dirlist.first_index_for_viewport(layout_rect[1].height as usize); + let new_index = skip_dist + (y - layout_rect[1].y - 1) as usize; + if let Err(e) = if is_parent { + parent_cursor_move::parent_cursor_move(new_index, context) + } else { + cursor_move::cursor_move(new_index, context) + } { + context.push_msg(e.to_string()); } } } else { @@ -118,12 +124,14 @@ FileOp::Copy => "copied", FileOp::Cut => "moved", }; - let size_str = format::file_size_to_string(progress.bytes_processed()); + let processed_size = format::file_size_to_string(progress.bytes_processed()); + let total_size = format::file_size_to_string(progress.total_bytes()); let msg = format!( - "successfully {} {} items ({})", + "successfully {} {} items ({}/{})", op, - progress.len(), - size_str + progress.total_files(), + processed_size, + total_size, ); context.push_msg(msg); } Только в a/src/util: load_child.rs diff -ur a/src/util/mod.rs b/src/util/mod.rs --- a/src/util/mod.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/util/mod.rs 2021-08-07 11:25:14.715474131 +0300 @@ -2,14 +2,13 @@ pub mod devicons; pub mod display; -pub mod event; pub mod format; pub mod input; -pub mod load_child; pub mod name_resolution; pub mod search; pub mod select; pub mod sort; +pub mod string; pub mod style; pub mod to_string; pub mod unix; diff -ur a/src/util/sort.rs b/src/util/sort.rs --- a/src/util/sort.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/util/sort.rs 2021-08-07 11:25:14.715474131 +0300 @@ -1,4 +1,5 @@ use std::cmp; +use std::collections::VecDeque; use std::fs; use std::time; @@ -6,12 +7,13 @@ use crate::fs::JoshutoDirEntry; -#[derive(Clone, Copy, Debug, Deserialize)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum SortType { Lexical, Mtime, Natural, Size, + Ext, } impl SortType { @@ -21,6 +23,7 @@ "mtime" => Some(SortType::Mtime), "natural" => Some(SortType::Natural), "size" => Some(SortType::Size), + "ext" => Some(SortType::Ext), _ => None, } } @@ -30,6 +33,21 @@ SortType::Mtime => "mtime", SortType::Natural => "natural", SortType::Size => "size", + SortType::Ext => "ext", + } + } + pub fn cmp( + &self, + f1: &JoshutoDirEntry, + f2: &JoshutoDirEntry, + sort_option: &SortOption, + ) -> cmp::Ordering { + match &self { + SortType::Natural => natural_sort(f1, f2, sort_option), + SortType::Lexical => lexical_sort(f1, f2, sort_option), + SortType::Size => size_sort(f1, f2), + SortType::Mtime => mtime_sort(f1, f2), + SortType::Ext => ext_sort(f1, f2), } } } @@ -41,14 +59,61 @@ } #[derive(Clone, Debug)] +pub struct SortTypes { + pub list: VecDeque, +} + +impl SortTypes { + pub fn reorganize(&mut self, st: SortType) { + self.list.push_front(st); + self.list.pop_back(); + } + + pub fn cmp( + &self, + f1: &JoshutoDirEntry, + f2: &JoshutoDirEntry, + sort_option: &SortOption, + ) -> cmp::Ordering { + for st in &self.list { + let res = st.cmp(f1, f2, sort_option); + if res != cmp::Ordering::Equal { + return res; + } + } + cmp::Ordering::Equal + } +} + +impl std::default::Default for SortTypes { + fn default() -> Self { + let list: VecDeque = vec![ + SortType::Natural, + SortType::Lexical, + SortType::Size, + SortType::Ext, + SortType::Mtime, + ] + .into_iter() + .collect(); + + Self { list } + } +} + +#[derive(Clone, Debug)] pub struct SortOption { pub directories_first: bool, pub case_sensitive: bool, pub reverse: bool, - pub sort_method: SortType, + pub sort_methods: SortTypes, } impl SortOption { + pub fn set_sort_method(&mut self, method: SortType) { + self.sort_methods.reorganize(method); + } + pub fn compare(&self, f1: &JoshutoDirEntry, f2: &JoshutoDirEntry) -> cmp::Ordering { if self.directories_first { let f1_isdir = f1.file_path().is_dir(); @@ -61,40 +126,15 @@ } } - let mut res = match self.sort_method { - SortType::Lexical => { - let f1_name = f1.file_name(); - let f2_name = f2.file_name(); - if self.case_sensitive { - f1_name.cmp(&f2_name) - } else { - let f1_name = f1_name.to_lowercase(); - let f2_name = f2_name.to_lowercase(); - f1_name.cmp(&f2_name) - } - } - SortType::Natural => { - let f1_name = f1.file_name(); - let f2_name = f2.file_name(); - if self.case_sensitive { - alphanumeric_sort::compare_str(&f1_name, &f2_name) - } else { - let f1_name = f1_name.to_lowercase(); - let f2_name = f2_name.to_lowercase(); - alphanumeric_sort::compare_str(&f1_name, &f2_name) - } - } - SortType::Mtime => mtime_sort(f1, f2), - SortType::Size => size_sort(f1, f2), - }; - + // let mut res = self.sort_method.cmp(f1, f2, &self); + let mut res = self.sort_methods.cmp(f1, f2, &self); if self.reverse { res = match res { cmp::Ordering::Less => cmp::Ordering::Greater, cmp::Ordering::Greater => cmp::Ordering::Less, s => s, }; - } + }; res } } @@ -105,7 +145,7 @@ directories_first: true, case_sensitive: false, reverse: false, - sort_method: SortType::Natural, + sort_methods: SortTypes::default(), } } } @@ -120,16 +160,49 @@ let f1_mtime: time::SystemTime = f1_meta.modified()?; let f2_mtime: time::SystemTime = f2_meta.modified()?; - - Ok(if f1_mtime >= f2_mtime { - cmp::Ordering::Less - } else { - cmp::Ordering::Greater - }) + Ok(f1_mtime.cmp(&f2_mtime)) } - compare(&file1, &file2).unwrap_or(cmp::Ordering::Less) + compare(&file1, &file2).unwrap_or(cmp::Ordering::Equal) } fn size_sort(file1: &JoshutoDirEntry, file2: &JoshutoDirEntry) -> cmp::Ordering { file1.metadata.len().cmp(&file2.metadata.len()) } + +fn ext_sort(file1: &JoshutoDirEntry, file2: &JoshutoDirEntry) -> cmp::Ordering { + let f1_ext = file1.get_ext(); + let f2_ext = file2.get_ext(); + alphanumeric_sort::compare_str(&f1_ext, &f2_ext) +} + +fn lexical_sort( + f1: &JoshutoDirEntry, + f2: &JoshutoDirEntry, + sort_option: &SortOption, +) -> cmp::Ordering { + let f1_name = f1.file_name(); + let f2_name = f2.file_name(); + if sort_option.case_sensitive { + f1_name.cmp(&f2_name) + } else { + let f1_name = f1_name.to_lowercase(); + let f2_name = f2_name.to_lowercase(); + f1_name.cmp(&f2_name) + } +} + +fn natural_sort( + f1: &JoshutoDirEntry, + f2: &JoshutoDirEntry, + sort_option: &SortOption, +) -> cmp::Ordering { + let f1_name = f1.file_name(); + let f2_name = f2.file_name(); + if sort_option.case_sensitive { + alphanumeric_sort::compare_str(&f1_name, &f2_name) + } else { + let f1_name = f1_name.to_lowercase(); + let f2_name = f2_name.to_lowercase(); + alphanumeric_sort::compare_str(&f1_name, &f2_name) + } +} Только в b/src/util: string.rs diff -ur a/src/util/style.rs b/src/util/style.rs --- a/src/util/style.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/util/style.rs 2021-08-07 11:25:14.715474131 +0300 @@ -1,6 +1,6 @@ use tui::style::Style; -use crate::fs::{FileType, JoshutoDirEntry}; +use crate::fs::{FileType, JoshutoDirEntry, LinkType}; use crate::util::unix; use crate::THEME_T; @@ -8,25 +8,39 @@ pub fn entry_style(entry: &JoshutoDirEntry) -> Style { let metadata = &entry.metadata; let filetype = &metadata.file_type(); + let linktype = &metadata.link_type(); - match filetype { - _ if entry.is_selected() => Style::default() + if entry.is_selected() { + Style::default() .fg(THEME_T.selection.fg) .bg(THEME_T.selection.bg) - .add_modifier(THEME_T.selection.modifier), - FileType::Directory => Style::default() - .fg(THEME_T.directory.fg) - .bg(THEME_T.directory.bg) - .add_modifier(THEME_T.directory.modifier), - FileType::Symlink(_) => Style::default() - .fg(THEME_T.link.fg) - .bg(THEME_T.link.bg) - .add_modifier(THEME_T.link.modifier), - _ if unix::is_executable(metadata.mode) => Style::default() + .add_modifier(THEME_T.selection.modifier) + } else { + match linktype { + LinkType::Symlink(_) => Style::default() + .fg(THEME_T.link.fg) + .bg(THEME_T.link.bg) + .add_modifier(THEME_T.link.modifier), + LinkType::Normal => match filetype { + FileType::Directory => Style::default() + .fg(THEME_T.directory.fg) + .bg(THEME_T.directory.bg) + .add_modifier(THEME_T.directory.modifier), + FileType::File => file_style(entry), + }, + } + } +} + +fn file_style(entry: &JoshutoDirEntry) -> Style { + let metadata = &entry.metadata; + if unix::is_executable(metadata.mode) { + Style::default() .fg(THEME_T.executable.fg) .bg(THEME_T.executable.bg) - .add_modifier(THEME_T.executable.modifier), - _ => match entry.file_path().extension() { + .add_modifier(THEME_T.executable.modifier) + } else { + match entry.file_path().extension() { None => Style::default(), Some(os_str) => match os_str.to_str() { None => Style::default(), @@ -35,6 +49,6 @@ Some(t) => Style::default().fg(t.fg).bg(t.bg).add_modifier(t.modifier), }, }, - }, + } } } diff -ur a/src/util/to_string.rs b/src/util/to_string.rs --- a/src/util/to_string.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/util/to_string.rs 2021-08-07 11:25:14.715474131 +0300 @@ -30,9 +30,8 @@ impl ToString for MouseEvent { fn to_string(&self) -> String { - match *self { - k => format!("{:?}", k), - } + let k = *self; + format!("{:?}", k) } } diff -ur a/src/util/unix.rs b/src/util/unix.rs --- a/src/util/unix.rs 2021-05-08 03:04:44.000000000 +0300 +++ b/src/util/unix.rs 2021-08-07 11:25:14.715474131 +0300 @@ -1,5 +1,3 @@ -use std::path::Path; - pub fn is_executable(mode: u32) -> bool { const LIBC_PERMISSION_VALS: [libc::mode_t; 3] = [libc::S_IXUSR, libc::S_IXGRP, libc::S_IXOTH]; @@ -51,13 +49,3 @@ } mode_str } - -pub fn set_mode(path: &Path, mode: u32) { - let os_path = path.as_os_str(); - if let Some(s) = os_path.to_str() { - let svec: Vec = s.bytes().map(|ch| ch as libc::c_char).collect(); - unsafe { - libc::chmod(svec.as_ptr(), mode as libc::mode_t); - } - } -}