From fcb579f4c231720f09599d7bea7e62ad81133dc6 Mon Sep 17 00:00:00 2001 From: Alexander Miroshnichenko Date: Sat, 7 Aug 2021 12:58:46 +0300 Subject: [PATCH] app-misc/joshuto: add patch changes up to 72aaf0c5d10db0004d48e27c58d18d8f2c568f8f --- app-misc/joshuto/Manifest | 3 +- ...f0c5d10db0004d48e27c58d18d8f2c568f8f.patch | 5082 +++++++++++++++++ app-misc/joshuto/joshuto-0.9.0.ebuild | 4 + 3 files changed, 5088 insertions(+), 1 deletion(-) create mode 100644 app-misc/joshuto/files/72aaf0c5d10db0004d48e27c58d18d8f2c568f8f.patch diff --git a/app-misc/joshuto/Manifest b/app-misc/joshuto/Manifest index 3fc1984..02fb4e8 100644 --- a/app-misc/joshuto/Manifest +++ b/app-misc/joshuto/Manifest @@ -1,3 +1,4 @@ +AUX 72aaf0c5d10db0004d48e27c58d18d8f2c568f8f.patch 163466 BLAKE2B 5b84539c7e27ae0e3fad0dd3e627f5720fbd013d2a1c9dc6c348f98a0b06b435b8f136ee534a56088b43e608da3b4b6214e756ec68155b4de2434c39d26df679 SHA512 b43cf2a0e796d5cae9ea00b23f3d1d3ef7efbfaff66aa400d24f232df9b277e0446f0a4f3c148f0a2d8f6d3d584f71dfc4b7fb8cbd2c1101af39b071a9b4d309 AUX 99joshuto 33 BLAKE2B 1e032f3bbfd3deb86e4dc380e84162eabaa00060f43184efca9d54cb948267e42d7a2d065827c459cb9a97d87d938c7ab2c4a0f22ca9c6bba01b4c46ef454633 SHA512 cd678cb36bf660ef3d60497f785cf4f991528c3baeb801f14027ddaca4ef96799ee7822725524283ed5e3d39f330c6e1670f3da9b5c90a449e971fb67f6c219f DIST aho-corasick-0.7.18.crate 112923 BLAKE2B 4f6947d1aacf89ccfab0592cdc55fa61ef09cea38231d3f758765dbce328a810c0b588be4ba96e81d64955379ee005722d22a7aec39caea6e72342245d7ca34f SHA512 7a23b16231a90d23ee60ad4c81bc225410599a4560d33d3a203138fc540c39cf1000100fed3aed40dcc371c3635656a3792545dca5dd1aefbde00d8774eebd00 DIST alphanumeric-sort-1.4.3.crate 5796 BLAKE2B 38383f7816ba73baf3ada54059daa808909d6560f904244ed7b6804ededcd13ce4b30aa04a79ac8bb3b226e905561037ecc827f9eb7ae255bf0237598d596e5e SHA512 9b4f0edb16ebf43b8411fcfb6f78c0934155ba7f68817f30ec0d1fde730b812af550f5e1a11411230b4f9074934971734724c39fe3779e1dc0aa9e61aa11292e @@ -162,4 +163,4 @@ DIST winapi-i686-pc-windows-gnu-0.4.0.crate 2918815 BLAKE2B 4d357e4d30f955297217 DIST winapi-util-0.1.5.crate 10164 BLAKE2B fc800aceae5249a858c806c3e969ef2545766099872d856ebee51c883d9acf1122278db9607d50ca53eac351502b700fd2463900932d342240f97f683d517963 SHA512 7baeb661f397c4693dfa001fdc774b323c51a7c55caad40f2de5112a1cefd1d6151e3df41fa4ee193460a5905917c83d2b1de5fa10b4bd014ad96690af95c0fd DIST winapi-x86_64-pc-windows-gnu-0.4.0.crate 2947998 BLAKE2B 2ad1ea8b5fa07d544e910ccba043ae925269b76b26c9da356305b34b86741dd8b9aff0b9ffe3d562db4fcd7d7c46a11ce9e3168b782b1d89ae6881742b7ede82 SHA512 4a654af6a5d649dc87e00497245096b35a2894ae66f155cb62389902c3b93ddcc5cf7d0d8b9dd97b291d2d80bc686af2298e80abef6ac69883f4a54e79712513 DIST xdg-2.2.0.crate 13387 BLAKE2B f1a5909b6a4544eaf28d1d051e05a6b9b09043060e14e394410778ed175e9f27246afadf3851dec21020c5e2ca81ef4db44a34e58d9229972b2475ff5f40d6c9 SHA512 16342b8396b4a329801a0ce3b9db82c4c767d09339f895d9f000c64b7160df11019b320cda632f99d8d5b9f65c97894b09091ee4e2ccafac0cd671481fa5c61e -EBUILD joshuto-0.9.0.ebuild 3532 BLAKE2B 788bce52aadb934157dad6c79304117b82dce7fc5f90fc02db0f118838e11c2bffb38e9564f15e8ed98ab25dddf45022070a6b3f8d0630a130c97ee328bd185a SHA512 cb83027500dc6c25ef16a03d8d256bfcfb730266d17b3a2017be98093f49360f974f5fa32e02da761c2914060687e49481b817e6efc4b8526068c1d6d731d9c6 +EBUILD joshuto-0.9.0.ebuild 3605 BLAKE2B f9a8d02bfae930eee42a9ab607152ba6c5c298fee8f1963d2a48fcfad66fcfb01b16e7dbd13c69d50bf09d2d9c4c6a9ca436857c19844e4df678c47db4b6c7aa SHA512 211acefe252415392f60cc33bc5f0cd46dd72cbb21802026ee6ac387a511e32826bb63e19e8bd88ed83e7eae1aee3270068ab569a2aa61c2ea342a9516e8d195 diff --git a/app-misc/joshuto/files/72aaf0c5d10db0004d48e27c58d18d8f2c568f8f.patch b/app-misc/joshuto/files/72aaf0c5d10db0004d48e27c58d18d8f2c568f8f.patch new file mode 100644 index 0000000..19b7a5a --- /dev/null +++ b/app-misc/joshuto/files/72aaf0c5d10db0004d48e27c58d18d8f2c568f8f.patch @@ -0,0 +1,5082 @@ +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); +- } +- } +-} diff --git a/app-misc/joshuto/joshuto-0.9.0.ebuild b/app-misc/joshuto/joshuto-0.9.0.ebuild index aaaacc8..44e1dca 100644 --- a/app-misc/joshuto/joshuto-0.9.0.ebuild +++ b/app-misc/joshuto/joshuto-0.9.0.ebuild @@ -184,6 +184,10 @@ DEPEND="" RDEPEND="wayland? ( gui-apps/wl-clipboard ) X? ( x11-misc/xclip )" +PATCHES=( + ${FILESDIR}/72aaf0c5d10db0004d48e27c58d18d8f2c568f8f.patch +) + src_install() { cargo_src_install