From 37f22b67af5e0a928ed020d7a217121ab332804c Mon Sep 17 00:00:00 2001 From: graelo Date: Sun, 24 May 2020 21:02:11 +0200 Subject: [PATCH] feat: copyrat --- .rustfmt.toml | 15 +- Cargo.lock | 175 +++++++++---- Cargo.toml | 17 +- src/alphabets.rs | 46 ++-- src/colors.rs | 54 ++-- src/error.rs | 18 ++ src/main.rs | 354 ++++++++++---------------- src/state.rs | 75 ++++-- src/swapper.rs | 631 +++++++++++++++++++++++++---------------------- src/view.rs | 181 ++++++++------ tmux-thumbs.sh | 2 +- 11 files changed, 840 insertions(+), 728 deletions(-) create mode 100644 src/error.rs diff --git a/.rustfmt.toml b/.rustfmt.toml index 44f8e3a..39587b2 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,2 +1,13 @@ -tab_spaces = 2 -max_width = 120 +format_code_in_doc_comments = true +match_block_trailing_comma = true +condense_wildcard_suffixes = true +use_field_init_shorthand = true +overflow_delimited_expr = true +# width_heuristics = "Max" +normalize_comments = true +reorder_impl_items = true +use_try_shorthand = true +newline_style = "Unix" +format_strings = true +wrap_comments = true +# comment_width = 100 diff --git a/Cargo.lock b/Cargo.lock index d17caff..e972d7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,14 +8,6 @@ dependencies = [ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "atty" version = "0.2.14" @@ -26,6 +18,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "1.2.1" @@ -33,18 +30,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" -version = "2.33.0" +version = "3.0.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap_derive 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "os_str_bytes 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "term_size 1.0.0-beta1 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clap_derive" +version = "3.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "copyrat" +version = "0.1.0" +dependencies = [ + "clap 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "heck" version = "0.3.1" @@ -61,6 +84,23 @@ dependencies = [ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "indexmap" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -81,12 +121,17 @@ name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "os_str_bytes" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro-error" -version = "1.0.2" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-error-attr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", @@ -95,7 +140,7 @@ dependencies = [ [[package]] name = "proc-macro-error-attr" -version = "1.0.2" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -152,31 +197,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "structopt" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "structopt-derive" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-error 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "syn" version = "1.0.23" @@ -197,6 +220,33 @@ dependencies = [ "syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "term_size" +version = "1.0.0-beta1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "termion" version = "1.5.5" @@ -213,6 +263,7 @@ name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -224,16 +275,6 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "thumbs" -version = "0.4.1" -dependencies = [ - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "unicode-segmentation" version = "1.6.0" @@ -259,6 +300,11 @@ name = "version_check" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.3.8" @@ -268,11 +314,24 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -280,29 +339,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum clap 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)" = "860643c53f980f0d38a5e25dfab6c3c93b2cb3aa1fe192643d17a293c6c41936" +"checksum clap_derive 3.0.0-beta.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fb51c9e75b94452505acd21d929323f5a5c6c4735a852adbd39ef5fb1b014f30" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" +"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" -"checksum proc-macro-error 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98e9e4b82e0ef281812565ea4751049f1bdcdfccda7d3f459f2e138a40c08678" -"checksum proc-macro-error-attr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4f5444ead4e9935abd7f27dc51f7e852a0569ac888096d5ec2499470794e2e53" +"checksum os_str_bytes 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "06de47b848347d8c4c94219ad8ecd35eb90231704b067e67e6ae2e36ee023510" +"checksum proc-macro-error 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" +"checksum proc-macro-error-attr 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" "checksum proc-macro2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "de40dd4ff82d9c9bab6dae29dbab1167e515f8df9ed17d2987cb6012db206933" "checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" "checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" -"checksum structopt-derive 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d239ca4b13aee7a2142e6795cbd69e457665ff8037aed33b3effdc430d2f927a" +"checksum strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" "checksum syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269" "checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +"checksum term_size 1.0.0-beta1 (registry+https://github.com/rust-lang/crates.io-index)" = "a8a17d8699e154863becdf18e4fd28bd0be27ca72856f54daf75c00f2566898f" +"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" "checksum termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" @@ -311,6 +375,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 96e2c6e..b3ed495 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,22 @@ [package] -name = "thumbs" -version = "0.4.1" -authors = ["Ferran Basora "] +name = "copyrat" +version = "0.1.0" +authors = ["Ferran Basora ", "u0xy "] edition = "2018" -description = "A lightning fast version copy/pasting like vimium/vimperator" +description = "This is tmux-copycat on Rust steroids." repository = "https://github.com/fcsonline/tmux-thumbs" -keywords = ["rust", "tmux", "tmux-plugin", "vimium", "vimperator"] +keywords = ["rust", "tmux", "tmux-plugin", "tmux-copycat"] license = "MIT" [dependencies] termion = "1.5" regex = "1.3.1" -clap = "2.33.0" -structopt = { version = "0.3", default-features = false } +clap = { version = "3.0.0-beta.1", features = ["suggestions", "color", "wrap_help", "term_size"]} [[bin]] -name = "thumbs" +name = "copyrat" path = "src/main.rs" [[bin]] -name = "tmux-thumbs" +name = "tmux-copyrat" path = "src/swapper.rs" diff --git a/src/alphabets.rs b/src/alphabets.rs index 25c1040..7d9897c 100644 --- a/src/alphabets.rs +++ b/src/alphabets.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use crate::error; const ALPHABETS: [(&'static str, &'static str); 22] = [ ("numeric", "1234567890"), @@ -25,17 +25,17 @@ const ALPHABETS: [(&'static str, &'static str); 22] = [ ("colemak-right-hand", "neioluymjhk"), ]; -pub struct Alphabet<'a> { - letters: &'a str, -} +// pub struct Alphabet<'a> { +// letters: &'a str, +// } -impl<'a> Alphabet<'a> { - fn new(letters: &'a str) -> Alphabet { - Alphabet { letters } - } +/// Type-safe string alphabet (newtype). +#[derive(Debug)] +pub struct Alphabet(pub String); +impl Alphabet { pub fn hints(&self, matches: usize) -> Vec { - let letters: Vec = self.letters.chars().map(|s| s.to_string()).collect(); + let letters: Vec = self.0.chars().map(|s| s.to_string()).collect(); let mut expansion = letters.clone(); let mut expanded: Vec = Vec::new(); @@ -64,14 +64,22 @@ impl<'a> Alphabet<'a> { } } -pub fn get_alphabet(alphabet_name: &str) -> Alphabet { - let alphabets: HashMap<&str, &str> = ALPHABETS.iter().cloned().collect(); +// pub fn get_alphabet(alphabet_name: &str) -> Alphabet { +// let alphabets: HashMap<&str, &str> = ALPHABETS.iter().cloned().collect(); - alphabets - .get(alphabet_name) - .expect(format!("Unknown alphabet: {}", alphabet_name).as_str()); // FIXME +// alphabets +// .get(alphabet_name) +// .expect(format!("Unknown alphabet: {}", alphabet_name).as_str()); - Alphabet::new(alphabets[alphabet_name]) +// Alphabet::new(alphabets[alphabet_name]) +// } + +pub fn parse_alphabet(src: &str) -> Result { + let alphabet = ALPHABETS.iter().find(|&(name, _letters)| name == &src); + match alphabet { + Some((_name, letters)) => Ok(Alphabet(letters.to_string())), + None => Err(error::ParseError::UnknownAlphabet), + } } #[cfg(test)] @@ -80,28 +88,28 @@ mod tests { #[test] fn simple_matches() { - let alphabet = Alphabet::new("abcd"); + let alphabet = Alphabet("abcd".to_string()); let hints = alphabet.hints(3); assert_eq!(hints, ["a", "b", "c"]); } #[test] fn composed_matches() { - let alphabet = Alphabet::new("abcd"); + let alphabet = Alphabet("abcd".to_string()); let hints = alphabet.hints(6); assert_eq!(hints, ["a", "b", "c", "da", "db", "dc"]); } #[test] fn composed_matches_multiple() { - let alphabet = Alphabet::new("abcd"); + let alphabet = Alphabet("abcd".to_string()); let hints = alphabet.hints(8); assert_eq!(hints, ["a", "b", "ca", "cb", "da", "db", "dc", "dd"]); } #[test] fn composed_matches_max() { - let alphabet = Alphabet::new("ab"); + let alphabet = Alphabet("ab".to_string()); let hints = alphabet.hints(8); assert_eq!(hints, ["aa", "ab", "ba", "bb"]); } diff --git a/src/colors.rs b/src/colors.rs index be5f92d..b72b9e9 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,35 +1,39 @@ +use crate::error; use termion::color; -pub fn get_color(color_name: &str) -> Box<&dyn color::Color> { - match color_name { - "black" => Box::new(&color::Black), - "red" => Box::new(&color::Red), - "green" => Box::new(&color::Green), - "yellow" => Box::new(&color::Yellow), - "blue" => Box::new(&color::Blue), - "magenta" => Box::new(&color::Magenta), - "cyan" => Box::new(&color::Cyan), - "white" => Box::new(&color::White), - "default" => Box::new(&color::Reset), - _ => panic!("Unknown color: {}", color_name), - } +pub fn parse_color(src: &str) -> Result, error::ParseError> { + match src { + "black" => Ok(Box::new(color::Black)), + "red" => Ok(Box::new(color::Red)), + "green" => Ok(Box::new(color::Green)), + "yellow" => Ok(Box::new(color::Yellow)), + "blue" => Ok(Box::new(color::Blue)), + "magenta" => Ok(Box::new(color::Magenta)), + "cyan" => Ok(Box::new(color::Cyan)), + "white" => Ok(Box::new(color::White)), + // "default" => Ok(Box::new(color::Reset)), + _ => Err(error::ParseError::UnknownColor), + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn match_color() { - let text1 = println!("{}{}", color::Fg(*get_color("green")), "foo"); - let text2 = println!("{}{}", color::Fg(color::Green), "foo"); + #[test] + fn match_color() { + let text1 = format!( + "{}{}", + color::Fg(parse_color("green").unwrap().as_ref()), + "foo" + ); + let text2 = format!("{}{}", color::Fg(color::Green), "foo"); - assert_eq!(text1, text2); - } + assert_eq!(text1, text2); + } - #[test] - #[should_panic] - fn no_match_color() { - println!("{}{}", color::Fg(*get_color("wat")), "foo"); - } + #[test] + fn no_match_color() { + assert!(parse_color("wat").is_err(), "this color should not exist"); + } } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..8ec3818 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,18 @@ +use std::fmt; + +#[derive(Debug)] +pub enum ParseError { + ExpectedSurroundingPair, + UnknownAlphabet, + UnknownColor, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ParseError::ExpectedSurroundingPair => write!(f, "Expected 2 chars"), + ParseError::UnknownAlphabet => write!(f, "Expected a known alphabet"), + ParseError::UnknownColor => write!(f, "Expected ANSI color name (magenta, cyan, black, ...)"), + } + } +} diff --git a/src/main.rs b/src/main.rs index 0983e29..86d02dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,239 +1,159 @@ -extern crate clap; -extern crate termion; - -mod alphabets; -mod colors; -mod state; -mod view; - -use self::clap::{App, Arg}; -use clap::crate_version; +use clap::Clap; use std::fs::OpenOptions; use std::io::prelude::*; use std::io::{self, Read}; -use structopt::StructOpt; +use std::path; -// TODO: position as an enum ::Leading ::Trailing +mod alphabets; +mod colors; +mod error; +mod state; +mod view; -/// A lightning fast version copy/pasting like vimium/vimperator. -#[derive(StructOpt, Debug)] -#[structopt(name = "thumbs")] +/// Main configuration, parsed from command line. +#[derive(Clap, Debug)] +#[clap(author, about, version)] struct Opt { - /// Sets the alphabet. - #[structopt(short, long, default_value = "qwerty")] - alphabet: String, + /// Alphabet to draw hints from. + /// + /// Possible values are "{A}", "{A}-homerow", "{A}-left-hand", + /// "{A}-right-hand", where "{A}" is one of "qwerty", "azerty", "qwertz", + /// "dvorak", "colemak". Examples: "qwerty", "dvorak-homerow". + #[clap(short = "k", long, default_value = "qwerty", + parse(try_from_str = alphabets::parse_alphabet))] + alphabet: alphabets::Alphabet, - /// Sets the foreground color for matches. - #[structopt(long, default_value = "green")] - fg_color: String, + /// Enable multi-selection. + #[clap(short, long)] + multi_selection: bool, - /// Sets the background color for matches. - #[structopt(long, default_value = "black")] - bg_color: String, + #[clap(flatten)] + colors: view::ViewColors, + + /// Reverse the order for assigned hints. + #[clap(short, long)] + reverse: bool, + + /// Keep the same hint for identical matches. + #[clap(short, long)] + unique: bool, + + /// Align hint with its match. + #[clap(short = "a", long, arg_enum, default_value = "Leading")] + hint_alignment: view::HintAlignment, + + /// Additional regex patterns. + #[clap(short = "c", long)] + custom_regex: Vec, + + /// Optional hint styling. + /// + /// Underline or surround the hint for increased visibility. + /// If not provided, only the hint colors will be used. + #[clap(short = "s", long, arg_enum)] + hint_style: Option, + + /// Chars surrounding each hint, used with `Surrounded` style. + #[clap(long, default_value = "{}", + parse(try_from_str = parse_chars))] + hint_surroundings: (char, char), + + /// Target path where to store the selected matches. + #[clap(short = "o", long = "output", parse(from_os_str))] + target_path: Option, + + /// Only output if key was uppercased. + #[clap(long)] + uppercased: bool, } -fn app_args<'a>() -> clap::ArgMatches<'a> { - App::new("thumbs") - .version(crate_version!()) - .about("A lightning fast version copy/pasting like vimium/vimperator") - .arg( - Arg::with_name("alphabet") - .help("Sets the alphabet") - .long("alphabet") - .short("a") - .default_value("qwerty"), - ) - .arg( - Arg::with_name("format") - .help("Specifies the out format for the picked hint. (%U: Upcase, %H: Hint)") - .long("format") - .short("f") - .default_value("%H"), - ) - .arg( - Arg::with_name("foreground_color") - .help("Sets the foregroud color for matches") - .long("fg-color") - .default_value("green"), - ) - .arg( - Arg::with_name("background_color") - .help("Sets the background color for matches") - .long("bg-color") - .default_value("black"), - ) - .arg( - Arg::with_name("hint_foreground_color") - .help("Sets the foregroud color for hints") - .long("hint-fg-color") - .default_value("yellow"), - ) - .arg( - Arg::with_name("hint_background_color") - .help("Sets the background color for hints") - .long("hint-bg-color") - .default_value("black"), - ) - .arg( - Arg::with_name("select_foreground_color") - .help("Sets the foreground color for selection") - .long("select-fg-color") - .default_value("blue"), - ) - .arg( - Arg::with_name("select_background_color") - .help("Sets the background color for selection") - .long("select-bg-color") - .default_value("black"), - ) - .arg( - Arg::with_name("multi") - .help("Enable multi-selection") - .long("multi") - .short("m"), - ) - .arg( - Arg::with_name("reverse") - .help("Reverse the order for assigned hints") - .long("reverse") - .short("r"), - ) - .arg( - Arg::with_name("unique") - .help("Don't show duplicated hints for the same match") - .long("unique") - .short("u"), - ) - .arg( - Arg::with_name("position") - .help("Hint position") - .long("position") - .default_value("left") - .short("p"), - ) - .arg( - Arg::with_name("regexp") - .help("Use this regexp as extra pattern to match") - .long("regexp") - .short("x") - .takes_value(true) - .multiple(true), - ) - .arg( - Arg::with_name("contrast") - .help("Put square brackets around hint for visibility") - .long("contrast") - .short("c"), - ) - .arg( - Arg::with_name("target") - .help("Stores the hint in the specified path") - .long("target") - .short("t") - .takes_value(true), - ) - .get_matches() +/// Type introduced due to parsing limitation, +/// as we cannot directly parse into view::HintStyle. +#[derive(Debug, Clap)] +enum HintStyleCli { + Underlined, + Surrounded, +} + +fn parse_chars(src: &str) -> Result<(char, char), error::ParseError> { + if src.len() != 2 { + return Err(error::ParseError::ExpectedSurroundingPair); + } + + let chars: Vec = src.chars().collect(); + Ok((chars[0], chars[1])) } fn main() { - let args = app_args(); - let format = args.value_of("format").unwrap(); - let alphabet = args.value_of("alphabet").unwrap(); - let position = args.value_of("position").unwrap(); - let target = args.value_of("target"); - let multi = args.is_present("multi"); - let reverse = args.is_present("reverse"); - let unique = args.is_present("unique"); - let contrast = args.is_present("contrast"); - let regexp = if let Some(items) = args.values_of("regexp") { - items.collect::>() - } else { - [].to_vec() - }; + let opt = Opt::parse(); - let foreground_color = colors::get_color(args.value_of("foreground_color").unwrap()); - let background_color = colors::get_color(args.value_of("background_color").unwrap()); - let hint_foreground_color = colors::get_color(args.value_of("hint_foreground_color").unwrap()); - let hint_background_color = colors::get_color(args.value_of("hint_background_color").unwrap()); - let select_foreground_color = colors::get_color(args.value_of("select_foreground_color").unwrap()); - let select_background_color = colors::get_color(args.value_of("select_background_color").unwrap()); + // Copy the pane contents (piped in via stdin) into a buffer, and split lines. + let stdin = io::stdin(); + let mut handle = stdin.lock(); - // Copy the pane contents (piped in via stdin) into a buffer, and split lines. - let mut buffer = String::new(); - let stdin = io::stdin(); - let mut handle = stdin.lock(); + let mut buffer = String::new(); + handle.read_to_string(&mut buffer).unwrap(); + let lines: Vec<&str> = buffer.split('\n').collect(); - handle.read_to_string(&mut buffer).unwrap(); + let mut state = state::State::new(&lines, &opt.alphabet, &opt.custom_regex); - let lines: Vec<&str> = buffer.split('\n').collect(); + let hint_style = match opt.hint_style { + None => None, + Some(style) => match style { + HintStyleCli::Underlined => Some(view::HintStyle::Underlined), + HintStyleCli::Surrounded => { + let (open, close) = opt.hint_surroundings; + Some(view::HintStyle::Surrounded(open, close)) + } + }, + }; + let uppercase_flag = opt.uppercased; - let mut state = state::State::new(&lines, alphabet, ®exp); + let selections = { + let mut viewbox = view::View::new( + &mut state, + opt.multi_selection, + opt.reverse, + opt.unique, + opt.hint_alignment, + &opt.colors, + hint_style, + ); - let hint_alignment = if position == "left" { - view::HintAlignment::Leading - } else { - view::HintAlignment::Trailing - }; + viewbox.present() + }; - let rendering_colors = view::ViewColors { - focus_fg: select_foreground_color, - focus_bg: select_background_color, - match_fg: foreground_color, - match_bg: background_color, - hint_fg: hint_foreground_color, - hint_bg: hint_background_color, - }; - - let hint_style = if contrast { - Some(view::HintStyle::Surrounded('[', ']')) - } else { - None - }; - - let selections = { - let mut viewbox = view::View::new( - &mut state, - multi, - reverse, - unique, - hint_alignment, - &rendering_colors, - hint_style, - ); - - viewbox.present() - }; - - // Early exit, signaling tmux we had no selections. - if selections.is_empty() { - ::std::process::exit(1); - } - - let output = selections - .iter() - .map(|(text, upcase)| { - let upcase_value = if *upcase { "true" } else { "false" }; - - let mut output = format.to_string(); - - output = str::replace(&output, "%U", upcase_value); - output = str::replace(&output, "%H", text.as_str()); - output - }) - .collect::>() - .join("\n"); - - match target { - None => println!("{}", output), - Some(target) => { - let mut file = OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(target) - .expect("Unable to open the target file"); - - file.write(output.as_bytes()).unwrap(); + // Early exit, signaling tmux we had no selections. + if selections.is_empty() { + ::std::process::exit(1); + } + + let output = selections + .iter() + .map(|(text, uppercased)| { + let upcase_value = if *uppercased { "true" } else { "false" }; + + let output = if uppercase_flag { upcase_value } else { text }; + // let mut output = &opt.format; + + // output = str::replace(&output, "%U", upcase_value); + // output = str::replace(&output, "%H", text.as_str()); + output + }) + .collect::>() + .join("\n"); + + match opt.target_path { + None => println!("{}", output), + Some(target) => { + let mut file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(target) + .expect("Unable to open the target file"); + + file.write(output.as_bytes()).unwrap(); + } } - } } diff --git a/src/state.rs b/src/state.rs index 022c61f..69db1bc 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,3 +1,4 @@ +use super::alphabets::Alphabet; use regex::Regex; use std::collections::HashMap; use std::fmt; @@ -52,12 +53,12 @@ impl<'a> PartialEq for Match<'a> { pub struct State<'a> { pub lines: &'a Vec<&'a str>, - alphabet: &'a str, - regexp: &'a Vec<&'a str>, + alphabet: &'a Alphabet, + regexp: &'a Vec, } impl<'a> State<'a> { - pub fn new(lines: &'a Vec<&'a str>, alphabet: &'a str, regexp: &'a Vec<&'a str>) -> State<'a> { + pub fn new(lines: &'a Vec<&'a str>, alphabet: &'a Alphabet, regexp: &'a Vec) -> State<'a> { State { lines, alphabet, @@ -133,8 +134,8 @@ impl<'a> State<'a> { } } - let alphabet = super::alphabets::get_alphabet(self.alphabet); - let mut hints = alphabet.hints(matches.len()); + // let alphabet = super::alphabets::get_alphabet(self.alphabet); + let mut hints = self.alphabet.hints(matches.len()); // This looks wrong but we do a pop after if !reverse { @@ -174,6 +175,7 @@ impl<'a> State<'a> { #[cfg(test)] mod tests { use super::*; + use crate::alphabets::Alphabet; fn split(output: &str) -> Vec<&str> { output.split("\n").collect::>() @@ -183,7 +185,8 @@ mod tests { fn match_reverse() { let lines = split("lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 3); assert_eq!(results.first().unwrap().hint.clone().unwrap(), "a"); @@ -194,7 +197,8 @@ mod tests { fn match_unique() { let lines = split("lorem 127.0.0.1 lorem 255.255.255.255 lorem 127.0.0.1 lorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, true); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, true); assert_eq!(results.len(), 3); assert_eq!(results.first().unwrap().hint.clone().unwrap(), "a"); @@ -205,7 +209,8 @@ mod tests { fn match_docker() { let lines = split("latest sha256:30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 1); assert_eq!( @@ -218,7 +223,8 @@ mod tests { fn match_bash() { let lines = split("path: /var/log/nginx.log\npath: test/log/nginx-2.log:32folder/.nginx@4df2.log"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text, "/var/log/nginx.log"); @@ -230,7 +236,8 @@ mod tests { fn match_paths() { let lines = split("Lorem /tmp/foo/bar_lol, lorem\n Lorem /var/log/boot-strap.log lorem ../log/kern.log lorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text.clone(), "/tmp/foo/bar_lol"); @@ -242,7 +249,8 @@ mod tests { fn match_home() { let lines = split("Lorem ~/.gnu/.config.txt, lorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().text.clone(), "~/.gnu/.config.txt"); @@ -252,7 +260,8 @@ mod tests { fn match_uids() { let lines = split("Lorem ipsum 123e4567-e89b-12d3-a456-426655440000 lorem\n Lorem lorem lorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 1); } @@ -261,7 +270,8 @@ mod tests { fn match_shas() { let lines = split("Lorem fd70b5695 5246ddf f924213 lorem\n Lorem 973113963b491874ab2e372ee60d4b4cb75f717c lorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text.clone(), "fd70b5695"); @@ -277,7 +287,8 @@ mod tests { fn match_ips() { let lines = split("Lorem ipsum 127.0.0.1 lorem\n Lorem 255.255.10.255 lorem 127.0.0.1 lorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text.clone(), "127.0.0.1"); @@ -289,7 +300,8 @@ mod tests { fn match_ipv6s() { let lines = split("Lorem ipsum fe80::2:202:fe4 lorem\n Lorem 2001:67c:670:202:7ba8:5e41:1591:d723 lorem fe80::2:1 lorem ipsum fe80:22:312:fe::1%eth0"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text.clone(), "fe80::2:202:fe4"); @@ -305,7 +317,8 @@ mod tests { fn match_markdown_urls() { let lines = split("Lorem ipsum [link](https://github.io?foo=bar) ![](http://cdn.com/img.jpg) lorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 2); assert_eq!(results.get(0).unwrap().pattern.clone(), "markdown_url"); @@ -318,7 +331,8 @@ mod tests { fn match_urls() { let lines = split("Lorem ipsum https://www.rust-lang.org/tools lorem\n Lorem ipsumhttps://crates.io lorem https://github.io?foo=bar lorem ssh://github.io"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text.clone(), "https://www.rust-lang.org/tools"); @@ -335,7 +349,8 @@ mod tests { fn match_addresses() { let lines = split("Lorem 0xfd70b5695 0x5246ddf lorem\n Lorem 0x973113tlorem"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 3); assert_eq!(results.get(0).unwrap().text.clone(), "0xfd70b5695"); @@ -347,7 +362,8 @@ mod tests { fn match_hex_colors() { let lines = split("Lorem #fd7b56 lorem #FF00FF\n Lorem #00fF05 lorem #abcd00 lorem #afRR00"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 4); assert_eq!(results.get(0).unwrap().text.clone(), "#fd7b56"); @@ -360,7 +376,8 @@ mod tests { fn match_ipfs() { let lines = split("Lorem QmRdbNSxDJBXmssAc9fvTtux4duptMvfSGiGuq6yHAQVKQ lorem Qmfoobar"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 1); assert_eq!( @@ -374,7 +391,8 @@ mod tests { let lines = split("Lorem 5695 52463 lorem\n Lorem 973113 lorem 99999 lorem 8888 lorem\n 23456 lorem 5432 lorem 23444"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 8); } @@ -383,7 +401,8 @@ mod tests { fn match_diff_a() { let lines = split("Lorem lorem\n--- a/src/main.rs"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().text.clone(), "src/main.rs"); @@ -393,7 +412,8 @@ mod tests { fn match_diff_b() { let lines = split("Lorem lorem\n+++ b/src/main.rs"); let custom = [].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 1); assert_eq!(results.get(0).unwrap().text.clone(), "src/main.rs"); @@ -402,8 +422,13 @@ mod tests { #[test] fn priority() { let lines = split("Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem"); - let custom = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"].to_vec(); - let results = State::new(&lines, "abcd", &custom).matches(false, false); + + let custom: Vec = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"] + .iter() + .map(|&s| s.to_string()) + .collect(); + let alphabet = Alphabet("abcd".to_string()); + let results = State::new(&lines, &alphabet, &custom).matches(false, false); assert_eq!(results.len(), 9); assert_eq!(results.get(0).unwrap().text.clone(), "http://foo.bar"); diff --git a/src/swapper.rs b/src/swapper.rs index f5994fa..a1f221e 100644 --- a/src/swapper.rs +++ b/src/swapper.rs @@ -1,384 +1,417 @@ -extern crate clap; - -use self::clap::{App, Arg}; -use clap::crate_version; +use clap::Clap; use regex::Regex; +use std::path; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; trait Executor { - fn execute(&mut self, args: Vec) -> String; - fn last_executed(&self) -> Option>; + fn execute(&mut self, args: Vec) -> String; + fn last_executed(&self) -> Option>; } struct RealShell { - executed: Option>, + executed: Option>, } impl RealShell { - fn new() -> RealShell { - RealShell { executed: None } - } + fn new() -> RealShell { + RealShell { executed: None } + } } impl Executor for RealShell { - fn execute(&mut self, args: Vec) -> String { - let execution = Command::new(args[0].as_str()) - .args(&args[1..]) - .output() - .expect("Couldn't run it"); + fn execute(&mut self, args: Vec) -> String { + let execution = Command::new(args[0].as_str()) + .args(&args[1..]) + .output() + .expect("Execution failed"); - self.executed = Some(args); + self.executed = Some(args); - let output: String = String::from_utf8_lossy(&execution.stdout).into(); + let output: String = String::from_utf8_lossy(&execution.stdout).into(); - output.trim_end().to_string() - } + output.trim_end().to_string() + } - fn last_executed(&self) -> Option> { - self.executed.clone() - } + fn last_executed(&self) -> Option> { + self.executed.clone() + } } const TMP_FILE: &str = "/tmp/thumbs-last"; pub struct Swapper<'a> { - executor: Box<&'a mut dyn Executor>, - dir: String, - command: String, - upcase_command: String, - active_pane_id: Option, - active_pane_height: Option, - active_pane_scroll_position: Option, - active_pane_in_copy_mode: Option, - thumbs_pane_id: Option, - content: Option, - signal: String, + executor: Box<&'a mut dyn Executor>, + directory: &'a path::Path, + command: &'a str, + alt_command: &'a str, + active_pane_id: Option, + active_pane_height: Option, + active_pane_scroll_position: Option, + active_pane_in_copy_mode: Option, + thumbs_pane_id: Option, + content: Option, + signal: String, } impl<'a> Swapper<'a> { - fn new(executor: Box<&'a mut dyn Executor>, dir: String, command: String, upcase_command: String) -> Swapper { - let since_the_epoch = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards"); - let signal = format!("thumbs-finished-{}", since_the_epoch.as_secs()); + fn new( + executor: Box<&'a mut dyn Executor>, + directory: &'a path::Path, + command: &'a str, + alt_command: &'a str, + ) -> Swapper<'a> { + let since_the_epoch = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + let signal = format!("thumbs-finished-{}", since_the_epoch.as_secs()); - Swapper { - executor, - dir, - command, - upcase_command, - active_pane_id: None, - active_pane_height: None, - active_pane_scroll_position: None, - active_pane_in_copy_mode: None, - thumbs_pane_id: None, - content: None, - signal, + Swapper { + executor, + directory, + command, + alt_command, + active_pane_id: None, + active_pane_height: None, + active_pane_scroll_position: None, + active_pane_in_copy_mode: None, + thumbs_pane_id: None, + content: None, + signal, + } } - } - pub fn capture_active_pane(&mut self) { - let active_command = vec![ + pub fn capture_active_pane(&mut self) { + let active_command = vec![ "tmux", "list-panes", "-F", "#{pane_id}:#{?pane_in_mode,1,0}:#{pane_height}:#{scroll_position}:#{?pane_active,active,nope}", ]; - let output = self - .executor - .execute(active_command.iter().map(|arg| arg.to_string()).collect()); + let output = self + .executor + .execute(active_command.iter().map(|arg| arg.to_string()).collect()); - let lines: Vec<&str> = output.split('\n').collect(); - let chunks: Vec> = lines.into_iter().map(|line| line.split(':').collect()).collect(); + let lines: Vec<&str> = output.split('\n').collect(); + let chunks: Vec> = lines + .into_iter() + .map(|line| line.split(':').collect()) + .collect(); - let active_pane = chunks - .iter() - .find(|&chunks| *chunks.get(4).unwrap() == "active") - .expect("Unable to find active pane"); + let active_pane = chunks + .iter() + .find(|&chunks| *chunks.get(4).unwrap() == "active") + .expect("Unable to find active pane"); - let pane_id = active_pane.get(0).unwrap(); - let pane_in_copy_mode = active_pane.get(1).unwrap().to_string(); + let pane_id = active_pane.get(0).unwrap(); + let pane_in_copy_mode = active_pane.get(1).unwrap().to_string(); - self.active_pane_id = Some(pane_id.to_string()); - self.active_pane_in_copy_mode = Some(pane_in_copy_mode); + self.active_pane_id = Some(pane_id.to_string()); + self.active_pane_in_copy_mode = Some(pane_in_copy_mode); - if self.active_pane_in_copy_mode.clone().unwrap() == "1" { - let pane_height = active_pane - .get(2) - .unwrap() - .parse() - .expect("Unable to retrieve pane height"); - let pane_scroll_position = active_pane - .get(3) - .unwrap() - .parse() - .expect("Unable to retrieve pane scroll"); + if self.active_pane_in_copy_mode.clone().unwrap() == "1" { + let pane_height = active_pane + .get(2) + .unwrap() + .parse() + .expect("Unable to retrieve pane height"); + let pane_scroll_position = active_pane + .get(3) + .unwrap() + .parse() + .expect("Unable to retrieve pane scroll"); - self.active_pane_height = Some(pane_height); - self.active_pane_scroll_position = Some(pane_scroll_position); - } - } - - pub fn execute_thumbs(&mut self) { - let options_command = vec!["tmux", "show", "-g"]; - let params: Vec = options_command.iter().map(|arg| arg.to_string()).collect(); - let options = self.executor.execute(params); - let lines: Vec<&str> = options.split('\n').collect(); - - let pattern = Regex::new(r#"@thumbs-([\w\-0-9]+) "?(\w+)"?"#).unwrap(); - - let args = lines - .iter() - .flat_map(|line| { - if let Some(captures) = pattern.captures(line) { - let name = captures.get(1).unwrap().as_str(); - let value = captures.get(2).unwrap().as_str(); - - let boolean_params = vec!["reverse", "unique", "contrast"]; - - if boolean_params.iter().any(|&x| x == name) { - return vec![format!("--{}", name)]; - } - - let string_params = vec![ - "position", - "fg-color", - "bg-color", - "hint-bg-color", - "hint-fg-color", - "select-fg-color", - "select-bg-color", - ]; - - if string_params.iter().any(|&x| x == name) { - return vec![format!("--{}", name), format!("'{}'", value)]; - } - - if name.starts_with("regexp") { - return vec!["--regexp".to_string(), format!("'{}'", value)]; - } - - vec![] - } else { - vec![] + self.active_pane_height = Some(pane_height); + self.active_pane_scroll_position = Some(pane_scroll_position); } - }) - .collect::>(); + } - let active_pane_id = self.active_pane_id.as_mut().unwrap().clone(); + pub fn execute_thumbs(&mut self) { + let options_command = vec!["tmux", "show", "-g"]; + let params: Vec = options_command.iter().map(|arg| arg.to_string()).collect(); + let options = self.executor.execute(params); + let lines: Vec<&str> = options.split('\n').collect(); - let scroll_params = if self.active_pane_in_copy_mode.is_some() { - if let (Some(pane_height), Some(scroll_position)) = - (self.active_pane_scroll_position, self.active_pane_scroll_position) - { - format!(" -S {} -E {}", -scroll_position, pane_height - scroll_position - 1) - } else { - "".to_string() - } - } else { - "".to_string() - }; + let pattern = Regex::new(r#"@thumbs-([\w\-0-9]+) "?(\w+)"?"#).unwrap(); - // NOTE: For debugging add echo $PWD && sleep 5 after tee - let pane_command = format!( + let args = lines + .iter() + .flat_map(|line| { + if let Some(captures) = pattern.captures(line) { + let name = captures.get(1).unwrap().as_str(); + let value = captures.get(2).unwrap().as_str(); + + let boolean_params = vec!["reverse", "unique", "contrast"]; + + if boolean_params.iter().any(|&x| x == name) { + return vec![format!("--{}", name)]; + } + + let string_params = vec![ + "position", + "fg-color", + "bg-color", + "hint-bg-color", + "hint-fg-color", + "select-fg-color", + "select-bg-color", + ]; + + if string_params.iter().any(|&x| x == name) { + return vec![format!("--{}", name), format!("'{}'", value)]; + } + + if name.starts_with("regexp") { + return vec!["--regexp".to_string(), format!("'{}'", value)]; + } + + vec![] + } else { + vec![] + } + }) + .collect::>(); + + let active_pane_id = self.active_pane_id.as_mut().unwrap().clone(); + + let scroll_params = if self.active_pane_in_copy_mode.is_some() { + if let (Some(pane_height), Some(scroll_position)) = ( + self.active_pane_scroll_position, + self.active_pane_scroll_position, + ) { + format!( + " -S {} -E {}", + -scroll_position, + pane_height - scroll_position - 1 + ) + } else { + "".to_string() + } + } else { + "".to_string() + }; + + // NOTE: For debugging add echo $PWD && sleep 5 after tee + let pane_command = format!( "tmux capture-pane -t {} -p{} | {}/target/release/thumbs -f '%U:%H' -t {} {}; tmux swap-pane -t {}; tmux wait-for -S {}", active_pane_id, scroll_params, - self.dir, + self.directory.to_str().unwrap(), TMP_FILE, args.join(" "), active_pane_id, self.signal ); - let thumbs_command = vec![ - "tmux", - "new-window", - "-P", - "-d", - "-n", - "[thumbs]", - pane_command.as_str(), - ]; + let thumbs_command = vec![ + "tmux", + "new-window", + "-P", + "-d", + "-n", + "[thumbs]", + pane_command.as_str(), + ]; - let params: Vec = thumbs_command.iter().map(|arg| arg.to_string()).collect(); + let params: Vec = thumbs_command.iter().map(|arg| arg.to_string()).collect(); - self.thumbs_pane_id = Some(self.executor.execute(params)); - } + self.thumbs_pane_id = Some(self.executor.execute(params)); + } - pub fn swap_panes(&mut self) { - let active_pane_id = self.active_pane_id.as_mut().unwrap().clone(); - let thumbs_pane_id = self.thumbs_pane_id.as_mut().unwrap().clone(); + pub fn swap_panes(&mut self) { + let active_pane_id = self.active_pane_id.as_mut().unwrap().clone(); + let thumbs_pane_id = self.thumbs_pane_id.as_mut().unwrap().clone(); - let swap_command = vec![ - "tmux", - "swap-pane", - "-d", - "-s", - active_pane_id.as_str(), - "-t", - thumbs_pane_id.as_str(), - ]; - let params = swap_command.iter().map(|arg| arg.to_string()).collect(); + let swap_command = vec![ + "tmux", + "swap-pane", + "-d", + "-s", + active_pane_id.as_str(), + "-t", + thumbs_pane_id.as_str(), + ]; + let params = swap_command.iter().map(|arg| arg.to_string()).collect(); - self.executor.execute(params); - } + self.executor.execute(params); + } - pub fn wait_thumbs(&mut self) { - let wait_command = vec!["tmux", "wait-for", self.signal.as_str()]; - let params = wait_command.iter().map(|arg| arg.to_string()).collect(); + pub fn wait_thumbs(&mut self) { + let wait_command = vec!["tmux", "wait-for", self.signal.as_str()]; + let params = wait_command.iter().map(|arg| arg.to_string()).collect(); - self.executor.execute(params); - } + self.executor.execute(params); + } - pub fn retrieve_content(&mut self) { - let retrieve_command = vec!["cat", TMP_FILE]; - let params = retrieve_command.iter().map(|arg| arg.to_string()).collect(); + pub fn retrieve_content(&mut self) { + let retrieve_command = vec!["cat", TMP_FILE]; + let params = retrieve_command.iter().map(|arg| arg.to_string()).collect(); - self.content = Some(self.executor.execute(params)); - } + self.content = Some(self.executor.execute(params)); + } - pub fn destroy_content(&mut self) { - let retrieve_command = vec!["rm", TMP_FILE]; - let params = retrieve_command.iter().map(|arg| arg.to_string()).collect(); - - self.executor.execute(params); - } - - pub fn execute_command(&mut self) { - let content = self.content.clone().unwrap(); - let mut splitter = content.splitn(2, ':'); - - if let Some(upcase) = splitter.next() { - if let Some(text) = splitter.next() { - let execute_command = if upcase.trim_end() == "true" { - self.upcase_command.clone() - } else { - self.command.clone() - }; - - let final_command = str::replace(execute_command.as_str(), "{}", text.trim_end()); - let retrieve_command = vec!["bash", "-c", final_command.as_str()]; + pub fn destroy_content(&mut self) { + let retrieve_command = vec!["rm", TMP_FILE]; let params = retrieve_command.iter().map(|arg| arg.to_string()).collect(); self.executor.execute(params); - } } - } + + pub fn execute_command(&mut self) { + let content = self.content.clone().unwrap(); + let mut splitter = content.splitn(2, ':'); + + if let Some(upcase) = splitter.next() { + if let Some(text) = splitter.next() { + let execute_command = if upcase.trim_end() == "true" { + self.alt_command.clone() + } else { + self.command.clone() + }; + + let final_command = str::replace(execute_command, "{}", text.trim_end()); + let retrieve_command = vec!["bash", "-c", final_command.as_str()]; + let params = retrieve_command.iter().map(|arg| arg.to_string()).collect(); + + self.executor.execute(params); + } + } + } } #[cfg(test)] mod tests { - use super::*; + use super::*; - struct TestShell { - outputs: Vec, - executed: Option>, - } - - impl TestShell { - fn new(outputs: Vec) -> TestShell { - TestShell { - executed: None, - outputs, - } - } - } - - impl Executor for TestShell { - fn execute(&mut self, args: Vec) -> String { - self.executed = Some(args); - self.outputs.pop().unwrap() + struct TestShell { + outputs: Vec, + executed: Option>, } - fn last_executed(&self) -> Option> { - self.executed.clone() + impl TestShell { + fn new(outputs: Vec) -> TestShell { + TestShell { + executed: None, + outputs, + } + } } - } - #[test] - fn retrieve_active_pane() { - let last_command_outputs = vec!["%97:100:24:1:active\n%106:100:24:1:nope\n%107:100:24:1:nope\n".to_string()]; - let mut executor = TestShell::new(last_command_outputs); - let mut swapper = Swapper::new(Box::new(&mut executor), "".to_string(), "".to_string(), "".to_string()); + impl Executor for TestShell { + fn execute(&mut self, args: Vec) -> String { + self.executed = Some(args); + self.outputs.pop().unwrap() + } - swapper.capture_active_pane(); + fn last_executed(&self) -> Option> { + self.executed.clone() + } + } - assert_eq!(swapper.active_pane_id.unwrap(), "%97"); - } + #[test] + fn retrieve_active_pane() { + let last_command_outputs = + vec!["%97:100:24:1:active\n%106:100:24:1:nope\n%107:100:24:1:nope\n".to_string()]; + let mut executor = TestShell::new(last_command_outputs); + let mut swapper = Swapper::new(Box::new(&mut executor), &path::Path::new(""), "", ""); - #[test] - fn swap_panes() { - let last_command_outputs = vec![ - "".to_string(), - "%100".to_string(), - "".to_string(), - "%106:100:24:1:nope\n%98:100:24:1:active\n%107:100:24:1:nope\n".to_string(), - ]; - let mut executor = TestShell::new(last_command_outputs); - let mut swapper = Swapper::new(Box::new(&mut executor), "".to_string(), "".to_string(), "".to_string()); + swapper.capture_active_pane(); + + assert_eq!(swapper.active_pane_id.unwrap(), "%97"); + } + + #[test] + fn swap_panes() { + let last_command_outputs = vec![ + "".to_string(), + "%100".to_string(), + "".to_string(), + "%106:100:24:1:nope\n%98:100:24:1:active\n%107:100:24:1:nope\n".to_string(), + ]; + let mut executor = TestShell::new(last_command_outputs); + let mut swapper = Swapper::new(Box::new(&mut executor), &path::Path::new(""), "", ""); + + swapper.capture_active_pane(); + swapper.execute_thumbs(); + swapper.swap_panes(); + + let expectation = vec!["tmux", "swap-pane", "-d", "-s", "%98", "-t", "%100"]; + + assert_eq!(executor.last_executed().unwrap(), expectation); + } +} + +/// Main configuration, parsed from command line. +#[derive(Clap, Debug)] +#[clap(author, about, version)] +struct Opt { + /// Directory where to execute copyrat. + #[clap(long, required = true)] + directory: path::PathBuf, + + /// Command to execute on selection. + #[clap(short, long, default_value = "'tmux set-buffer {}'")] + command: String, + + /// Command to execute on uppercased selection. + #[clap( + short, + long, + default_value = "'tmux set-bufffer {} && tmux-paste-buffer'" + )] + alt_command: String, +} + +// fn app_args<'a>() -> clap::ArgMatches<'a> { +// App::new("tmux-thumbs") +// .version(crate_version!()) +// .about("A lightning fast version of tmux-fingers, copy/pasting tmux like vimium/vimperator") +// .arg( +// Arg::with_name("dir") +// .help("Directory where to execute thumbs") +// .long("dir") +// .default_value(""), +// ) +// .arg( +// Arg::with_name("command") +// .help("Pick command") +// .long("command") +// .default_value("tmux set-buffer {}"), +// ) +// .arg( +// Arg::with_name("upcase_command") +// .help("Upcase command") +// .long("upcase-command") +// .default_value("tmux set-buffer {} && tmux paste-buffer"), +// ) +// .get_matches() +// } + +fn main() -> std::io::Result<()> { + let opt = Opt::parse(); + // let dir = args.value_of("dir").unwrap(); + // let command = args.value_of("command").unwrap(); + // let upcase_command = args.value_of("upcase_command").unwrap(); + + // if dir.is_empty() { + // panic!("Invalid tmux-thumbs execution. Are you trying to execute tmux-thumbs directly?") + // } + + let mut executor = RealShell::new(); + let mut swapper = Swapper::new( + Box::new(&mut executor), + opt.directory.as_path(), + &opt.command, + &opt.alt_command, + ); swapper.capture_active_pane(); swapper.execute_thumbs(); swapper.swap_panes(); - - let expectation = vec!["tmux", "swap-pane", "-d", "-s", "%98", "-t", "%100"]; - - assert_eq!(executor.last_executed().unwrap(), expectation); - } -} - -fn app_args<'a>() -> clap::ArgMatches<'a> { - App::new("tmux-thumbs") - .version(crate_version!()) - .about("A lightning fast version of tmux-fingers, copy/pasting tmux like vimium/vimperator") - .arg( - Arg::with_name("dir") - .help("Directory where to execute thumbs") - .long("dir") - .default_value(""), - ) - .arg( - Arg::with_name("command") - .help("Pick command") - .long("command") - .default_value("tmux set-buffer {}"), - ) - .arg( - Arg::with_name("upcase_command") - .help("Upcase command") - .long("upcase-command") - .default_value("tmux set-buffer {} && tmux paste-buffer"), - ) - .get_matches() -} - -fn main() -> std::io::Result<()> { - let args = app_args(); - let dir = args.value_of("dir").unwrap(); - let command = args.value_of("command").unwrap(); - let upcase_command = args.value_of("upcase_command").unwrap(); - - if dir.is_empty() { - panic!("Invalid tmux-thumbs execution. Are you trying to execute tmux-thumbs directly?") - } - - let mut executor = RealShell::new(); - let mut swapper = Swapper::new( - Box::new(&mut executor), - dir.to_string(), - command.to_string(), - upcase_command.to_string(), - ); - - swapper.capture_active_pane(); - swapper.execute_thumbs(); - swapper.swap_panes(); - swapper.wait_thumbs(); - swapper.retrieve_content(); - swapper.destroy_content(); - swapper.execute_command(); - Ok(()) + swapper.wait_thumbs(); + swapper.retrieve_content(); + swapper.destroy_content(); + swapper.execute_command(); + Ok(()) } diff --git a/src/view.rs b/src/view.rs index 6d18c38..72e7c83 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,4 +1,5 @@ use super::{colors, state}; +use clap::Clap; use std::char; use std::io::{stdout, Read, Write}; use termion::async_stdin; @@ -14,7 +15,7 @@ pub struct View<'a> { focus_index: usize, multi: bool, hint_alignment: HintAlignment, - rendering_colors: &'a ViewColors<'a>, + rendering_colors: &'a ViewColors, hint_style: Option, } @@ -23,17 +24,42 @@ pub struct View<'a> { /// - `focus_*` colors are used to render the currently focused matched text. /// - `normal_*` colors are used to render other matched text. /// - `hint_*` colors are used to render the hints. -pub struct ViewColors<'a> { - pub focus_fg: Box<&'a dyn color::Color>, - pub focus_bg: Box<&'a dyn color::Color>, - pub match_fg: Box<&'a dyn color::Color>, - pub match_bg: Box<&'a dyn color::Color>, - pub hint_fg: Box<&'a dyn color::Color>, - pub hint_bg: Box<&'a dyn color::Color>, +#[derive(Clap, Debug)] +pub struct ViewColors { + /// Foreground color for matches. + #[clap(long, default_value = "green", + parse(try_from_str = colors::parse_color))] + match_fg: Box, + + /// Background color for matches. + #[clap(long, default_value = "black", + parse(try_from_str = colors::parse_color))] + match_bg: Box, + + /// Foreground color for the focused match. + #[clap(long, default_value = "blue", + parse(try_from_str = colors::parse_color))] + focused_fg: Box, + + /// Background color for the focused match. + #[clap(long, default_value = "black", + parse(try_from_str = colors::parse_color))] + focused_bg: Box, + + /// Foreground color for hints. + #[clap(long, default_value = "white", + parse(try_from_str = colors::parse_color))] + hint_fg: Box, + + /// Background color for hints. + #[clap(long, default_value = "black", + parse(try_from_str = colors::parse_color))] + hint_bg: Box, } /// Describes if, during rendering, a hint should aligned to the leading edge of /// the matched text, or to its trailing edge. +#[derive(Debug, Clap)] pub enum HintAlignment { Leading, Trailing, @@ -135,7 +161,7 @@ impl<'a> View<'a> { ) { // To help identify it, the match thas has focus is rendered with a dedicated color. let (text_fg_color, text_bg_color) = if focused { - (&colors.focus_fg, &colors.focus_bg) + (&colors.focused_fg, &colors.focused_bg) } else { (&colors.match_fg, &colors.match_bg) }; @@ -145,8 +171,8 @@ impl<'a> View<'a> { stdout, "{goto}{bg_color}{fg_color}{text}{fg_reset}{bg_reset}", goto = cursor::Goto(offset.0 as u16 + 1, offset.1 as u16 + 1), - fg_color = color::Fg(**text_fg_color), - bg_color = color::Bg(**text_bg_color), + fg_color = color::Fg(text_fg_color.as_ref()), + bg_color = color::Bg(text_bg_color.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset), text = &text, @@ -171,8 +197,8 @@ impl<'a> View<'a> { colors: &ViewColors, hint_style: &Option, ) { - let fg_color = color::Fg(*colors.hint_fg); - let bg_color = color::Bg(*colors.hint_bg); + let fg_color = color::Fg(colors.hint_fg.as_ref()); + let bg_color = color::Bg(colors.hint_bg.as_ref()); let fg_reset = color::Fg(color::Reset); let bg_reset = color::Bg(color::Reset); @@ -426,6 +452,7 @@ impl<'a> View<'a> { #[cfg(test)] mod tests { use super::*; + use crate::alphabets; #[test] fn test_render_all_lines() { @@ -461,12 +488,12 @@ path: /usr/local/bin/cargo"; let focused = true; let offset: (usize, usize) = (3, 1); let colors = ViewColors { - focus_fg: Box::new(&(color::Red)), - focus_bg: Box::new(&(color::Blue)), - match_fg: Box::new(&color::Green), - match_bg: Box::new(&color::Magenta), - hint_fg: Box::new(&color::Yellow), - hint_bg: Box::new(&color::Cyan), + focused_fg: Box::new(color::Red), + focused_bg: Box::new(color::Blue), + match_fg: Box::new(color::Green), + match_bg: Box::new(color::Magenta), + hint_fg: Box::new(color::Yellow), + hint_bg: Box::new(color::Cyan), }; View::render_matched_text(&mut writer, text, focused, offset, &colors); @@ -476,8 +503,8 @@ path: /usr/local/bin/cargo"; format!( "{goto}{bg}{fg}{text}{fg_reset}{bg_reset}", goto = cursor::Goto(4, 2), - fg = color::Fg(*colors.focus_fg), - bg = color::Bg(*colors.focus_bg), + fg = color::Fg(colors.focused_fg.as_ref()), + bg = color::Bg(colors.focused_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset), text = &text, @@ -493,12 +520,12 @@ path: /usr/local/bin/cargo"; let focused = false; let offset: (usize, usize) = (3, 1); let colors = ViewColors { - focus_fg: Box::new(&(color::Red)), - focus_bg: Box::new(&(color::Blue)), - match_fg: Box::new(&color::Green), - match_bg: Box::new(&color::Magenta), - hint_fg: Box::new(&color::Yellow), - hint_bg: Box::new(&color::Cyan), + focused_fg: Box::new(color::Red), + focused_bg: Box::new(color::Blue), + match_fg: Box::new(color::Green), + match_bg: Box::new(color::Magenta), + hint_fg: Box::new(color::Yellow), + hint_bg: Box::new(color::Cyan), }; View::render_matched_text(&mut writer, text, focused, offset, &colors); @@ -508,8 +535,8 @@ path: /usr/local/bin/cargo"; format!( "{goto}{bg}{fg}{text}{fg_reset}{bg_reset}", goto = cursor::Goto(4, 2), - fg = color::Fg(*colors.match_fg), - bg = color::Bg(*colors.match_bg), + fg = color::Fg(colors.match_fg.as_ref()), + bg = color::Bg(colors.match_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset), text = &text, @@ -524,12 +551,12 @@ path: /usr/local/bin/cargo"; let hint_text = "eo"; let offset: (usize, usize) = (3, 1); let colors = ViewColors { - focus_fg: Box::new(&(color::Red)), - focus_bg: Box::new(&(color::Blue)), - match_fg: Box::new(&color::Green), - match_bg: Box::new(&color::Magenta), - hint_fg: Box::new(&color::Yellow), - hint_bg: Box::new(&color::Cyan), + focused_fg: Box::new(color::Red), + focused_bg: Box::new(color::Blue), + match_fg: Box::new(color::Green), + match_bg: Box::new(color::Magenta), + hint_fg: Box::new(color::Yellow), + hint_bg: Box::new(color::Cyan), }; let extra_offset = 0; @@ -548,8 +575,8 @@ path: /usr/local/bin/cargo"; format!( "{goto}{bg}{fg}{text}{fg_reset}{bg_reset}", goto = cursor::Goto(4, 2), - fg = color::Fg(*colors.hint_fg), - bg = color::Bg(*colors.hint_bg), + fg = color::Fg(colors.hint_fg.as_ref()), + bg = color::Bg(colors.hint_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset), text = "eo", @@ -564,12 +591,12 @@ path: /usr/local/bin/cargo"; let hint_text = "eo"; let offset: (usize, usize) = (3, 1); let colors = ViewColors { - focus_fg: Box::new(&(color::Red)), - focus_bg: Box::new(&(color::Blue)), - match_fg: Box::new(&color::Green), - match_bg: Box::new(&color::Magenta), - hint_fg: Box::new(&color::Yellow), - hint_bg: Box::new(&color::Cyan), + focused_fg: Box::new(color::Red), + focused_bg: Box::new(color::Blue), + match_fg: Box::new(color::Green), + match_bg: Box::new(color::Magenta), + hint_fg: Box::new(color::Yellow), + hint_bg: Box::new(color::Cyan), }; let extra_offset = 0; @@ -588,8 +615,8 @@ path: /usr/local/bin/cargo"; format!( "{goto}{bg}{fg}{sty}{text}{sty_reset}{fg_reset}{bg_reset}", goto = cursor::Goto(4, 2), - fg = color::Fg(*colors.hint_fg), - bg = color::Bg(*colors.hint_bg), + fg = color::Fg(colors.hint_fg.as_ref()), + bg = color::Bg(colors.hint_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset), sty = style::Underline, @@ -606,12 +633,12 @@ path: /usr/local/bin/cargo"; let hint_text = "eo"; let offset: (usize, usize) = (3, 1); let colors = ViewColors { - focus_fg: Box::new(&(color::Red)), - focus_bg: Box::new(&(color::Blue)), - match_fg: Box::new(&color::Green), - match_bg: Box::new(&color::Magenta), - hint_fg: Box::new(&color::Yellow), - hint_bg: Box::new(&color::Cyan), + focused_fg: Box::new(color::Red), + focused_bg: Box::new(color::Blue), + match_fg: Box::new(color::Green), + match_bg: Box::new(color::Magenta), + hint_fg: Box::new(color::Yellow), + hint_bg: Box::new(color::Cyan), }; let extra_offset = 0; @@ -630,8 +657,8 @@ path: /usr/local/bin/cargo"; format!( "{goto}{bg}{fg}{bra}{text}{bra_close}{fg_reset}{bg_reset}", goto = cursor::Goto(4, 2), - fg = color::Fg(*colors.hint_fg), - bg = color::Bg(*colors.hint_bg), + fg = color::Fg(colors.hint_fg.as_ref()), + bg = color::Bg(colors.hint_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset), bra = '{', @@ -652,15 +679,15 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let lines = content.split('\n').collect(); let custom_regexes = [].to_vec(); - let alphabet = "abcd"; - let mut state = state::State::new(&lines, alphabet, &custom_regexes); + let alphabet = alphabets::Alphabet("abcd".to_string()); + let mut state = state::State::new(&lines, &alphabet, &custom_regexes); let rendering_colors = ViewColors { - focus_fg: Box::new(&(color::Red)), - focus_bg: Box::new(&(color::Blue)), - match_fg: Box::new(&color::Green), - match_bg: Box::new(&color::Magenta), - hint_fg: Box::new(&color::Yellow), - hint_bg: Box::new(&color::Cyan), + focused_fg: Box::new(color::Red), + focused_bg: Box::new(color::Blue), + match_fg: Box::new(color::Green), + match_bg: Box::new(color::Magenta), + hint_fg: Box::new(color::Yellow), + hint_bg: Box::new(color::Cyan), }; let hint_alignment = HintAlignment::Leading; @@ -709,19 +736,19 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; let lines = content.split('\n').collect(); let custom_regexes = [].to_vec(); - let alphabet = "abcd"; - let mut state = state::State::new(&lines, alphabet, &custom_regexes); + let alphabet = alphabets::Alphabet("abcd".to_string()); + let mut state = state::State::new(&lines, &alphabet, &custom_regexes); let multi = false; let reversed = true; let unique = false; let rendering_colors = ViewColors { - focus_fg: Box::new(&(color::Red)), - focus_bg: Box::new(&(color::Blue)), - match_fg: Box::new(&color::Green), - match_bg: Box::new(&color::Magenta), - hint_fg: Box::new(&color::Yellow), - hint_bg: Box::new(&color::Cyan), + focused_fg: Box::new(color::Red), + focused_bg: Box::new(color::Blue), + match_fg: Box::new(color::Green), + match_bg: Box::new(color::Magenta), + hint_fg: Box::new(color::Yellow), + hint_bg: Box::new(color::Cyan), }; let hint_alignment = HintAlignment::Leading; let hint_style = None; @@ -758,8 +785,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; format!( "{goto7_1}{match_bg}{match_fg}127.0.0.1{fg_reset}{bg_reset}", goto7_1 = goto7_1, - match_fg = color::Fg(*rendering_colors.match_fg), - match_bg = color::Bg(*rendering_colors.match_bg), + match_fg = color::Fg(rendering_colors.match_fg.as_ref()), + match_bg = color::Bg(rendering_colors.match_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset) ) @@ -771,8 +798,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; format!( "{goto7_1}{hint_bg}{hint_fg}b{fg_reset}{bg_reset}", goto7_1 = goto7_1, - hint_fg = color::Fg(*rendering_colors.hint_fg), - hint_bg = color::Bg(*rendering_colors.hint_bg), + hint_fg = color::Fg(rendering_colors.hint_fg.as_ref()), + hint_bg = color::Bg(rendering_colors.hint_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset) ) @@ -783,8 +810,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; format!( "{goto11_3}{focus_bg}{focus_fg}https://en.wikipedia.org/wiki/Barcelona{fg_reset}{bg_reset}", goto11_3 = goto11_3, - focus_fg = color::Fg(*rendering_colors.focus_fg), - focus_bg = color::Bg(*rendering_colors.focus_bg), + focus_fg = color::Fg(rendering_colors.focused_fg.as_ref()), + focus_bg = color::Bg(rendering_colors.focused_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset) ) @@ -796,8 +823,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - "; format!( "{goto11_3}{hint_bg}{hint_fg}a{fg_reset}{bg_reset}", goto11_3 = goto11_3, - hint_fg = color::Fg(*rendering_colors.hint_fg), - hint_bg = color::Bg(*rendering_colors.hint_bg), + hint_fg = color::Fg(rendering_colors.hint_fg.as_ref()), + hint_bg = color::Bg(rendering_colors.hint_bg.as_ref()), fg_reset = color::Fg(color::Reset), bg_reset = color::Bg(color::Reset) ) diff --git a/tmux-thumbs.sh b/tmux-thumbs.sh index ec6b736..fce71f1 100755 --- a/tmux-thumbs.sh +++ b/tmux-thumbs.sh @@ -5,7 +5,7 @@ PARAMS=() function add-option-param { - VALUE=$(tmux show -vg @thumbs-$1 2> /dev/null) + VALUE=$(tmux show-options -vg @thumbs-$1 2> /dev/null) if [[ ${VALUE} ]]; then PARAMS+=("--$1=${VALUE}")