Browse Source

fix(cli.rs): inliner dialoguer & console until they publish, fixes #1492 (#1610)

* fix(cli.rs): inliner dialoguer & console until they publish, fixes #1492

* fix tests

* change file

* fix: tests & fmt

* chore: add fork note

* fix: windows deps

* chore(licensing): add note for console & dialoguer

Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com>

Co-authored-by: Daniel Thompson-Yvetot <denjell@mailscript.com>
Lucas Fernandes Nogueira 4 năm trước cách đây
mục cha
commit
d623d95fcb

+ 5 - 0
.changes/not-a-terminal-windows-fix.md

@@ -0,0 +1,5 @@
+---
+"cli.rs": patch
+---
+
+Fixes crash on usage of modifier keys on Windows when running `tauri init`.

+ 85 - 180
tooling/cli.rs/Cargo.lock

@@ -1,14 +1,5 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-[[package]]
-name = "addr2line"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
-dependencies = [
- "gimli",
-]
-
 [[package]]
 name = "adler"
 version = "1.0.2"
@@ -80,20 +71,6 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 
-[[package]]
-name = "backtrace"
-version = "0.3.56"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
-dependencies = [
- "addr2line",
- "cfg-if 1.0.0",
- "libc",
- "miniz_oxide 0.4.4",
- "object",
- "rustc-demangle",
-]
-
 [[package]]
 name = "base64"
 version = "0.13.0"
@@ -308,21 +285,6 @@ dependencies = [
  "unreachable",
 ]
 
-[[package]]
-name = "console"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
-dependencies = [
- "encode_unicode",
- "lazy_static",
- "libc",
- "regex",
- "terminal_size",
- "unicode-width",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "core-foundation"
 version = "0.9.1"
@@ -356,9 +318,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.0"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
+checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
 dependencies = [
  "cfg-if 1.0.0",
  "crossbeam-utils",
@@ -411,9 +373,9 @@ dependencies = [
 
 [[package]]
 name = "darling"
-version = "0.12.2"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a06d4a9551359071d1890820e3571252b91229e0712e7c36b08940e603c5a8fc"
+checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
 dependencies = [
  "darling_core",
  "darling_macro",
@@ -421,9 +383,9 @@ dependencies = [
 
 [[package]]
 name = "darling_core"
-version = "0.12.2"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b443e5fb0ddd56e0c9bfa47dc060c5306ee500cb731f2b91432dd65589a77684"
+checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
 dependencies = [
  "fnv",
  "ident_case",
@@ -435,9 +397,9 @@ dependencies = [
 
 [[package]]
 name = "darling_macro"
-version = "0.12.2"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0220073ce504f12a70efc4e7cdaea9e9b1b324872e7ad96a208056d7a638b81"
+checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
 dependencies = [
  "darling_core",
  "quote",
@@ -454,18 +416,6 @@ dependencies = [
  "byteorder",
 ]
 
-[[package]]
-name = "dialoguer"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9dd058f8b65922819fabb4a41e7d1964e56344042c26efbccd465202c23fa0c"
-dependencies = [
- "console",
- "lazy_static",
- "tempfile",
- "zeroize",
-]
-
 [[package]]
 name = "digest"
 version = "0.8.1"
@@ -523,28 +473,6 @@ version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
 
-[[package]]
-name = "failure"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
-dependencies = [
- "backtrace",
- "failure_derive",
-]
-
-[[package]]
-name = "failure_derive"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
-
 [[package]]
 name = "fake-simd"
 version = "0.1.2"
@@ -565,14 +493,14 @@ dependencies = [
 
 [[package]]
 name = "flate2"
-version = "1.0.14"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
+checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "crc32fast",
  "libc",
- "miniz_oxide 0.3.7",
+ "miniz_oxide 0.4.4",
 ]
 
 [[package]]
@@ -692,12 +620,6 @@ dependencies = [
  "weezl",
 ]
 
-[[package]]
-name = "gimli"
-version = "0.23.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
-
 [[package]]
 name = "glob"
 version = "0.3.0"
@@ -760,9 +682,9 @@ dependencies = [
 
 [[package]]
 name = "http"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747"
+checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
 dependencies = [
  "bytes",
  "fnv",
@@ -787,9 +709,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
 [[package]]
 name = "idna"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
 dependencies = [
  "matches",
  "unicode-bidi",
@@ -895,9 +817,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.49"
+version = "0.3.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821"
+checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -956,9 +878,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.91"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
+checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
 
 [[package]]
 name = "libflate"
@@ -1132,9 +1054,9 @@ dependencies = [
 
 [[package]]
 name = "notify"
-version = "4.0.15"
+version = "4.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd"
+checksum = "2599080e87c9bd051ddb11b10074f4da7b1223298df65d4c2ec5bcf309af1533"
 dependencies = [
  "bitflags",
  "filetime",
@@ -1199,12 +1121,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "object"
-version = "0.23.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
-
 [[package]]
 name = "once_cell"
 version = "1.7.2"
@@ -1258,9 +1174,9 @@ dependencies = [
 
 [[package]]
 name = "os_info"
-version = "3.0.1"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2127a5da3c69035537febc04cd07008bb643653303b213a49b036d944531207"
+checksum = "4181c9203e70e66f39def4240a7e33f74d1d95b42c08320f9ac298c580934ab2"
 dependencies = [
  "log",
  "serde",
@@ -1275,9 +1191,9 @@ checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
 
 [[package]]
 name = "pbkdf2"
-version = "0.7.3"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "309c95c5f738c85920eb7062a2de29f3840d4f96974453fc9ac1ba078da9c627"
+checksum = "bf916dd32dd26297907890d99dc2740e33f6bd9073965af4ccff2967962f5508"
 dependencies = [
  "crypto-mac",
 ]
@@ -1425,9 +1341,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.24"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
 dependencies = [
  "unicode-xid",
 ]
@@ -1575,9 +1491,9 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
+checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
 dependencies = [
  "bitflags",
 ]
@@ -1594,9 +1510,9 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "1.4.5"
+version = "1.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
+checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -1649,17 +1565,11 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
-[[package]]
-name = "rustc-demangle"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
-
 [[package]]
 name = "rustls"
-version = "0.19.0"
+version = "0.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
+checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
 dependencies = [
  "base64",
  "log",
@@ -1710,9 +1620,9 @@ dependencies = [
 
 [[package]]
 name = "schemars"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f81b69175bfb19653b59983e78701ebda5ee6a6f1e4875f65f9b92e2efde48f3"
+checksum = "bc6ab463ae35acccb5cba66c0084c985257b797d288b6050cc2f6ac1b266cb78"
 dependencies = [
  "dyn-clone",
  "schemars_derive",
@@ -1722,9 +1632,9 @@ dependencies = [
 
 [[package]]
 name = "schemars_derive"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3a9f7bf22f96bc98cebd33a9286118c4e588b6a2fc26ab83469c18e12ae7678"
+checksum = "902fdfbcf871ae8f653bddf4b2c05905ddaabc08f69d32a915787e3be0d31356"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1746,9 +1656,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
 [[package]]
 name = "scrypt"
-version = "0.6.4"
+version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25cba843abb27147110e2e9aa82b3be81eb386a9c6e3b35504c8c2ee305fd643"
+checksum = "19230d10daad7f163d8c1fc8edf84fbe52ac71c2ebe5adf3f763aa1557b843e3"
 dependencies = [
  "hmac",
  "pbkdf2",
@@ -1758,9 +1668,9 @@ dependencies = [
 
 [[package]]
 name = "sct"
-version = "0.6.0"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
+checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
 dependencies = [
  "ring",
  "untrusted",
@@ -1851,9 +1761,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "1.8.0"
+version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e557c650adfb38b32a5aec07082053253c703bc3cec654b27a5dbcf61995bb9b"
+checksum = "26b0b98f61935da47683bf5c46b965ce1642ef1db78860b8a1defb68bf1b5b43"
 dependencies = [
  "rustversion",
  "serde",
@@ -1921,9 +1831,9 @@ checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27"
 
 [[package]]
 name = "slab"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
 
 [[package]]
 name = "spin"
@@ -1945,37 +1855,25 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
 
 [[package]]
 name = "syn"
-version = "1.0.65"
+version = "1.0.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
+checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883"
 dependencies = [
  "proc-macro2",
  "quote",
  "unicode-xid",
 ]
 
-[[package]]
-name = "synstructure"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "unicode-xid",
-]
-
 [[package]]
 name = "sysctl"
-version = "0.4.0"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0501f0d0c2aa64b419abff97c209f4b82c4e67caa63e8dc5b222ecc1b574cb5c"
+checksum = "571282e1349eaf61f946466da731050daf3a9dcf74e33c7a0da065a665cabee7"
 dependencies = [
  "bitflags",
  "byteorder",
- "failure",
  "libc",
+ "thiserror",
  "walkdir",
 ]
 
@@ -2031,11 +1929,13 @@ dependencies = [
  "base64",
  "clap",
  "colored",
- "dialoguer",
+ "encode_unicode",
  "handlebars",
  "heck",
  "include_dir",
  "json-patch",
+ "lazy_static",
+ "libc",
  "minisign",
  "notify",
  "once_cell",
@@ -2048,10 +1948,15 @@ dependencies = [
  "serde_with",
  "shared_child",
  "tauri-bundler",
+ "tempfile",
+ "terminal_size",
  "toml",
  "toml_edit",
+ "unicode-width",
  "ureq",
  "valico",
+ "winapi 0.3.9",
+ "zeroize",
 ]
 
 [[package]]
@@ -2139,9 +2044,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "1.1.1"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
+checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
 dependencies = [
  "tinyvec_macros",
 ]
@@ -2204,9 +2109,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-bidi"
-version = "0.3.4"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
 dependencies = [
  "matches",
 ]
@@ -2324,9 +2229,9 @@ dependencies = [
 
 [[package]]
 name = "vcpkg"
-version = "0.2.11"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
+checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d"
 
 [[package]]
 name = "vec_map"
@@ -2371,9 +2276,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.72"
+version = "0.2.73"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe"
+checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
 dependencies = [
  "cfg-if 1.0.0",
  "wasm-bindgen-macro",
@@ -2381,9 +2286,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.72"
+version = "0.2.73"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3"
+checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
 dependencies = [
  "bumpalo",
  "lazy_static",
@@ -2396,9 +2301,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.72"
+version = "0.2.73"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b"
+checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -2406,9 +2311,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.72"
+version = "0.2.73"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d"
+checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2419,15 +2324,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.72"
+version = "0.2.73"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa"
+checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
 
 [[package]]
 name = "web-sys"
-version = "0.3.49"
+version = "0.3.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310"
+checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -2445,18 +2350,18 @@ dependencies = [
 
 [[package]]
 name = "webpki-roots"
-version = "0.21.0"
+version = "0.21.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376"
+checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
 dependencies = [
  "webpki",
 ]
 
 [[package]]
 name = "weezl"
-version = "0.1.4"
+version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a32b378380f4e9869b22f0b5177c68a5519f03b3454fde0b291455ddbae266c"
+checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"
 
 [[package]]
 name = "wildmatch"
@@ -2546,15 +2451,15 @@ dependencies = [
 
 [[package]]
 name = "zeroize"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
+checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
 
 [[package]]
 name = "zip"
-version = "0.5.11"
+version = "0.5.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8264fcea9b7a036a4a5103d7153e988dbc2ebbafb34f68a3c2d404b6b82d74b6"
+checksum = "9c83dc9b784d252127720168abd71ea82bf8c3d96b17dc565b5e2a02854f2b27"
 dependencies = [
  "byteorder",
  "bzip2",

+ 14 - 1
tooling/cli.rs/Cargo.toml

@@ -34,7 +34,6 @@ toml = "0.5.8"
 valico = "3.6"
 handlebars = "3.5"
 include_dir = "0.6"
-dialoguer = "0.8"
 minisign = "0.6"
 base64 = "0.13.0"
 ureq = "2.1"
@@ -42,6 +41,20 @@ os_info = "3.0"
 semver = "0.11"
 regex = "1.4"
 
+# console
+lazy_static = "1"
+libc = "0.2"
+terminal_size = "0.1.14"
+unicode-width = "0.1"
+# dialoguer
+tempfile = "3"
+zeroize = "1.1.1"
+
+# console
+[target.'cfg(windows)'.dependencies]
+winapi = { version = "0.3", features = ["winbase", "winuser", "consoleapi", "processenv", "wincon"] }
+encode_unicode = "0.3"
+
 [build-dependencies]
 schemars = "0.8"
 serde = { version = "1.0", features = [ "derive" ] }

+ 23 - 0
tooling/cli.rs/README.md

@@ -0,0 +1,23 @@
+# Tauri CLI
+
+## Licensing Errata:
+Because of publishing issues upstream, we soft-forked (and patched) both [`console`](https://github.com/mitsuhiko/console/blob/278de9dc2bf0fa28db69adee351072f668beec8f/Cargo.toml#L7) and [`dialoguer`](https://github.com/mitsuhiko/dialoguer/blob/2c3fe6b64641cfb57eb0e1d428274f63976ec150/Cargo.toml#L12) crates because of untenable issues surrounding expected use on Windows. 
+
+This soft fork was introduced to the Tauri Codebase [here](https://github.com/tauri-apps/tauri/pull/1610).
+
+`console`
+```
+license = "MIT"
+authors = [
+	"Armin Ronacher <armin.ronacher@active-4.com>"
+]
+```
+
+`dialoguer`
+```
+license = "MIT"
+authors = [
+	"Armin Ronacher <armin.ronacher@active-4.com>",
+	"Pavan Kumar Sunkara <pavan.sss1991@gmail.com>"
+]
+```

+ 137 - 0
tooling/cli.rs/src/console/ansi.rs

@@ -0,0 +1,137 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::borrow::Cow;
+
+use regex::{Matches, Regex};
+
+lazy_static::lazy_static! {
+    static ref STRIP_ANSI_RE: Regex =
+        Regex::new(r"[\x1b\x9b]([()][012AB]|[\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><])")
+            .unwrap();
+}
+
+/// Helper function to strip ansi codes.
+pub fn strip_ansi_codes(s: &str) -> Cow<str> {
+  STRIP_ANSI_RE.replace_all(s, "")
+}
+
+/// An iterator over ansi codes in a string.
+///
+/// This type can be used to scan over ansi codes in a string.
+/// It yields tuples in the form `(s, is_ansi)` where `s` is a slice of
+/// the original string and `is_ansi` indicates if the slice contains
+/// ansi codes or string values.
+pub struct AnsiCodeIterator<'a> {
+  s: &'a str,
+  pending_item: Option<(&'a str, bool)>,
+  last_idx: usize,
+  cur_idx: usize,
+  iter: Matches<'static, 'a>,
+}
+
+impl<'a> AnsiCodeIterator<'a> {
+  /// Creates a new ansi code iterator.
+  pub fn new(s: &'a str) -> AnsiCodeIterator<'a> {
+    AnsiCodeIterator {
+      s,
+      pending_item: None,
+      last_idx: 0,
+      cur_idx: 0,
+      iter: STRIP_ANSI_RE.find_iter(s),
+    }
+  }
+
+  /// Returns the string slice up to the current match.
+  pub fn current_slice(&self) -> &str {
+    &self.s[..self.cur_idx]
+  }
+
+  /// Returns the string slice from the current match to the end.
+  pub fn rest_slice(&self) -> &str {
+    &self.s[self.cur_idx..]
+  }
+}
+
+impl<'a> Iterator for AnsiCodeIterator<'a> {
+  type Item = (&'a str, bool);
+
+  fn next(&mut self) -> Option<(&'a str, bool)> {
+    if let Some(pending_item) = self.pending_item.take() {
+      self.cur_idx += pending_item.0.len();
+      Some(pending_item)
+    } else if let Some(m) = self.iter.next() {
+      let s = &self.s[self.last_idx..m.start()];
+      self.last_idx = m.end();
+      if s.is_empty() {
+        self.cur_idx = m.end();
+        Some((m.as_str(), true))
+      } else {
+        self.cur_idx = m.start();
+        self.pending_item = Some((m.as_str(), true));
+        Some((s, false))
+      }
+    } else if self.last_idx < self.s.len() {
+      let rv = &self.s[self.last_idx..];
+      self.cur_idx = self.s.len();
+      self.last_idx = self.s.len();
+      Some((rv, false))
+    } else {
+      None
+    }
+  }
+}
+
+#[test]
+fn test_ansi_iter_re_vt100() {
+  let s = "\x1b(0lpq\x1b)Benglish";
+  let mut iter = AnsiCodeIterator::new(s);
+  assert_eq!(iter.next(), Some(("\x1b(0", true)));
+  assert_eq!(iter.next(), Some(("lpq", false)));
+  assert_eq!(iter.next(), Some(("\x1b)B", true)));
+  assert_eq!(iter.next(), Some(("english", false)));
+}
+
+#[test]
+fn test_ansi_iter_re() {
+  use super::style;
+  let s = format!("Hello {}!", style("World").red().force_styling(true));
+  let mut iter = AnsiCodeIterator::new(&s);
+  assert_eq!(iter.next(), Some(("Hello ", false)));
+  assert_eq!(iter.current_slice(), "Hello ");
+  assert_eq!(iter.rest_slice(), "\x1b[31mWorld\x1b[0m!");
+  assert_eq!(iter.next(), Some(("\x1b[31m", true)));
+  assert_eq!(iter.current_slice(), "Hello \x1b[31m");
+  assert_eq!(iter.rest_slice(), "World\x1b[0m!");
+  assert_eq!(iter.next(), Some(("World", false)));
+  assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld");
+  assert_eq!(iter.rest_slice(), "\x1b[0m!");
+  assert_eq!(iter.next(), Some(("\x1b[0m", true)));
+  assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m");
+  assert_eq!(iter.rest_slice(), "!");
+  assert_eq!(iter.next(), Some(("!", false)));
+  assert_eq!(iter.current_slice(), "Hello \x1b[31mWorld\x1b[0m!");
+  assert_eq!(iter.rest_slice(), "");
+  assert_eq!(iter.next(), None);
+}
+
+#[test]
+fn test_ansi_iter_re_on_multi() {
+  use super::style;
+  let s = format!("{}", style("a").red().bold().force_styling(true));
+  let mut iter = AnsiCodeIterator::new(&s);
+  assert_eq!(iter.next(), Some(("\x1b[31m", true)));
+  assert_eq!(iter.current_slice(), "\x1b[31m");
+  assert_eq!(iter.rest_slice(), "\x1b[1ma\x1b[0m");
+  assert_eq!(iter.next(), Some(("\x1b[1m", true)));
+  assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1m");
+  assert_eq!(iter.rest_slice(), "a\x1b[0m");
+  assert_eq!(iter.next(), Some(("a", false)));
+  assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma");
+  assert_eq!(iter.rest_slice(), "\x1b[0m");
+  assert_eq!(iter.next(), Some(("\x1b[0m", true)));
+  assert_eq!(iter.current_slice(), "\x1b[31m\x1b[1ma\x1b[0m");
+  assert_eq!(iter.rest_slice(), "");
+  assert_eq!(iter.next(), None);
+}

+ 76 - 0
tooling/cli.rs/src/console/common_term.rs

@@ -0,0 +1,76 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::io;
+
+use super::term::Term;
+
+pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> {
+  if n > 0 {
+    out.write_str(&format!("\x1b[{}B", n))
+  } else {
+    Ok(())
+  }
+}
+
+pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> {
+  if n > 0 {
+    out.write_str(&format!("\x1b[{}A", n))
+  } else {
+    Ok(())
+  }
+}
+pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> {
+  if n > 0 {
+    out.write_str(&format!("\x1b[{}D", n))
+  } else {
+    Ok(())
+  }
+}
+
+pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> {
+  if n > 0 {
+    out.write_str(&format!("\x1b[{}C", n))
+  } else {
+    Ok(())
+  }
+}
+
+#[inline]
+pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> {
+  out.write_str(&format!("\x1B[{};{}H", y + 1, x + 1))
+}
+
+pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> {
+  if n > 0 {
+    out.write_str(&format!("\x1b[{}D\x1b[0K", n))
+  } else {
+    Ok(())
+  }
+}
+
+#[inline]
+pub fn clear_line(out: &Term) -> io::Result<()> {
+  out.write_str("\r\x1b[2K")
+}
+
+#[inline]
+pub fn clear_screen(out: &Term) -> io::Result<()> {
+  out.write_str("\r\x1b[2J\r\x1b[H")
+}
+
+#[inline]
+pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> {
+  out.write_str("\r\x1b[0J")
+}
+
+#[inline]
+pub fn show_cursor(out: &Term) -> io::Result<()> {
+  out.write_str("\x1b[?25h")
+}
+
+#[inline]
+pub fn hide_cursor(out: &Term) -> io::Result<()> {
+  out.write_str("\x1b[?25l")
+}

+ 32 - 0
tooling/cli.rs/src/console/kb.rs

@@ -0,0 +1,32 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+/// Key mapping
+///
+/// This is an incomplete mapping of keys that are supported for reading
+/// from the keyboard.
+#[non_exhaustive]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub enum Key {
+  Unknown,
+  /// Unrecognized sequence containing Esc and a list of chars
+  UnknownEscSeq(Vec<char>),
+  ArrowLeft,
+  ArrowRight,
+  ArrowUp,
+  ArrowDown,
+  Enter,
+  Escape,
+  Backspace,
+  Home,
+  End,
+  Tab,
+  BackTab,
+  Del,
+  Shift,
+  Insert,
+  PageUp,
+  PageDown,
+  Char(char),
+}

+ 108 - 0
tooling/cli.rs/src/console/mod.rs

@@ -0,0 +1,108 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+//! console is a library for Rust that provides access to various terminal
+//! features so you can build nicer looking command line interfaces.  It
+//! comes with various tools and utilities for working with Terminals and
+//! formatting text.
+//!
+//! Best paired with other libraries in the family:
+//!
+//! * [dialoguer](https://docs.rs/dialoguer)
+//! * [indicatif](https://docs.rs/indicatif)
+//!
+//! # Terminal Access
+//!
+//! The terminal is abstracted through the `console::Term` type.  It can
+//! either directly provide access to the connected terminal or by buffering
+//! up commands.  A buffered terminal will however not be completely buffered
+//! on windows where cursor movements are currently directly passed through.
+//!
+//! Example usage:
+//!
+//! ```
+//! # fn test() -> Result<(), Box<dyn std::error::Error>> {
+//! use std::thread;
+//! use std::time::Duration;
+//!
+//! use console::Term;
+//!
+//! let term = Term::stdout();
+//! term.write_line("Hello World!")?;
+//! thread::sleep(Duration::from_millis(2000));
+//! term.clear_line()?;
+//! # Ok(()) } test().unwrap();
+//! ```
+//!
+//! # Colors and Styles
+//!
+//! `console` uses `clicolors-control` to control colors.  It also
+//! provides higher level wrappers for styling text and other things
+//! that can be displayed with the `style` function and utility types.
+//!
+//! Example usage:
+//!
+//! ```
+//! use console::style;
+//!
+//! println!("This is {} neat", style("quite").cyan());
+//! ```
+//!
+//! You can also store styles and apply them to text later:
+//!
+//! ```
+//! use console::Style;
+//!
+//! let cyan = Style::new().cyan();
+//! println!("This is {} neat", cyan.apply_to("quite"));
+//! ```
+//!
+//! # Working with ANSI Codes
+//!
+//! The crate provids the function `strip_ansi_codes` to remove ANSI codes
+//! from a string as well as `measure_text_width` to calculate the width of a
+//! string as it would be displayed by the terminal.  Both of those together
+//! are useful for more complex formatting.
+//!
+//! # Unicode Width Support
+//!
+//! By default this crate depends on the `unicode-width` crate to calculate
+//! the width of terminal characters.  If you do not need this you can disable
+//! the `unicode-width` feature which will cut down on dependencies.
+//!
+//! # Features
+//!
+//! By default all features are enabled.  The following features exist:
+//!
+//! * `unicode-width`: adds support for unicode width calculations
+//! * `ansi-parsing`: adds support for parsing ansi codes (this adds support
+//!   for stripping and taking ansi escape codes into account for length
+//!   calculations).
+
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+pub use kb::Key;
+pub use term::{user_attended, user_attended_stderr, Term, TermFamily, TermFeatures, TermTarget};
+pub use utils::{
+  colors_enabled, colors_enabled_stderr, measure_text_width, pad_str, pad_str_with,
+  set_colors_enabled, set_colors_enabled_stderr, style, truncate_str, Alignment, Attribute, Color,
+  Emoji, Style, StyledObject,
+};
+
+pub use ansi::{strip_ansi_codes, AnsiCodeIterator};
+
+mod common_term;
+mod kb;
+mod term;
+#[cfg(unix)]
+mod unix_term;
+mod utils;
+#[cfg(target_arch = "wasm32")]
+mod wasm_term;
+#[cfg(windows)]
+mod windows_term;
+
+mod ansi;

+ 624 - 0
tooling/cli.rs/src/console/term.rs

@@ -0,0 +1,624 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::fmt::{Debug, Display};
+use std::io::{self, Read, Write};
+use std::sync::{Arc, Mutex};
+
+#[cfg(unix)]
+use std::os::unix::io::{AsRawFd, RawFd};
+#[cfg(windows)]
+use std::os::windows::io::{AsRawHandle, RawHandle};
+
+use super::{kb::Key, utils::Style};
+
+#[cfg(unix)]
+trait TermWrite: Write + Debug + AsRawFd + Send {}
+#[cfg(unix)]
+impl<T: Write + Debug + AsRawFd + Send> TermWrite for T {}
+
+#[cfg(unix)]
+trait TermRead: Read + Debug + AsRawFd + Send {}
+#[cfg(unix)]
+impl<T: Read + Debug + AsRawFd + Send> TermRead for T {}
+
+#[cfg(unix)]
+#[derive(Debug, Clone)]
+pub struct ReadWritePair {
+  read: Arc<Mutex<dyn TermRead>>,
+  write: Arc<Mutex<dyn TermWrite>>,
+  style: Style,
+}
+
+/// Where the term is writing.
+#[derive(Debug, Clone)]
+pub enum TermTarget {
+  Stdout,
+  Stderr,
+  #[cfg(unix)]
+  ReadWritePair(ReadWritePair),
+}
+
+#[derive(Debug)]
+pub struct TermInner {
+  target: TermTarget,
+  buffer: Option<Mutex<Vec<u8>>>,
+}
+
+/// The family of the terminal.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum TermFamily {
+  /// Redirected to a file or file like thing.
+  File,
+  /// A standard unix terminal.
+  UnixTerm,
+  /// A cmd.exe like windows console.
+  WindowsConsole,
+  /// A dummy terminal (for instance on wasm)
+  Dummy,
+}
+
+/// Gives access to the terminal features.
+#[derive(Debug, Clone)]
+pub struct TermFeatures<'a>(&'a Term);
+
+impl<'a> TermFeatures<'a> {
+  /// Checks if this is a real user attended terminal (`isatty`)
+  #[inline]
+  pub fn is_attended(&self) -> bool {
+    is_a_terminal(self.0)
+  }
+
+  /// Checks if colors are supported by this terminal.
+  ///
+  /// This does not check if colors are enabled.  Currently all terminals
+  /// are considered to support colors
+  #[inline]
+  pub fn colors_supported(&self) -> bool {
+    is_a_color_terminal(self.0)
+  }
+
+  /// Checks if this terminal is an msys terminal.
+  ///
+  /// This is sometimes useful to disable features that are known to not
+  /// work on msys terminals or require special handling.
+  #[inline]
+  pub fn is_msys_tty(&self) -> bool {
+    #[cfg(windows)]
+    {
+      msys_tty_on(&self.0)
+    }
+    #[cfg(not(windows))]
+    {
+      false
+    }
+  }
+
+  /// Checks if this terminal wants emojis.
+  #[inline]
+  pub fn wants_emoji(&self) -> bool {
+    self.is_attended() && wants_emoji()
+  }
+
+  /// Returns the family of the terminal.
+  #[inline]
+  pub fn family(&self) -> TermFamily {
+    if !self.is_attended() {
+      return TermFamily::File;
+    }
+    #[cfg(windows)]
+    {
+      TermFamily::WindowsConsole
+    }
+    #[cfg(unix)]
+    {
+      TermFamily::UnixTerm
+    }
+    #[cfg(target_arch = "wasm32")]
+    {
+      TermFamily::Dummy
+    }
+  }
+}
+
+/// Abstraction around a terminal.
+///
+/// A terminal can be cloned.  If a buffer is used it's shared across all
+/// clones which means it largely acts as a handle.
+#[derive(Clone, Debug)]
+pub struct Term {
+  inner: Arc<TermInner>,
+  pub(crate) is_msys_tty: bool,
+  pub(crate) is_tty: bool,
+}
+
+impl Term {
+  fn with_inner(inner: TermInner) -> Term {
+    let mut term = Term {
+      inner: Arc::new(inner),
+      is_msys_tty: false,
+      is_tty: false,
+    };
+
+    term.is_msys_tty = term.features().is_msys_tty();
+    term.is_tty = term.features().is_attended();
+    term
+  }
+
+  /// Return a new unbuffered terminal
+  #[inline]
+  pub fn stdout() -> Term {
+    Term::with_inner(TermInner {
+      target: TermTarget::Stdout,
+      buffer: None,
+    })
+  }
+
+  /// Return a new unbuffered terminal to stderr
+  #[inline]
+  pub fn stderr() -> Term {
+    Term::with_inner(TermInner {
+      target: TermTarget::Stderr,
+      buffer: None,
+    })
+  }
+
+  /// Return a new buffered terminal
+  pub fn buffered_stdout() -> Term {
+    Term::with_inner(TermInner {
+      target: TermTarget::Stdout,
+      buffer: Some(Mutex::new(vec![])),
+    })
+  }
+
+  /// Return a new buffered terminal to stderr
+  pub fn buffered_stderr() -> Term {
+    Term::with_inner(TermInner {
+      target: TermTarget::Stderr,
+      buffer: Some(Mutex::new(vec![])),
+    })
+  }
+
+  /// Return a terminal for the given Read/Write pair styled-like Stderr.
+  #[cfg(unix)]
+  pub fn read_write_pair<R, W>(read: R, write: W) -> Term
+  where
+    R: Read + Debug + AsRawFd + Send + 'static,
+    W: Write + Debug + AsRawFd + Send + 'static,
+  {
+    Self::read_write_pair_with_style(read, write, Style::new().for_stderr())
+  }
+
+  /// Return a terminal for the given Read/Write pair.
+  #[cfg(unix)]
+  pub fn read_write_pair_with_style<R, W>(read: R, write: W, style: Style) -> Term
+  where
+    R: Read + Debug + AsRawFd + Send + 'static,
+    W: Write + Debug + AsRawFd + Send + 'static,
+  {
+    Term::with_inner(TermInner {
+      target: TermTarget::ReadWritePair(ReadWritePair {
+        read: Arc::new(Mutex::new(read)),
+        write: Arc::new(Mutex::new(write)),
+        style,
+      }),
+      buffer: None,
+    })
+  }
+
+  /// Returns the style for the term
+  #[inline]
+  pub fn style(&self) -> Style {
+    match self.inner.target {
+      TermTarget::Stderr => Style::new().for_stderr(),
+      TermTarget::Stdout => Style::new().for_stdout(),
+      #[cfg(unix)]
+      TermTarget::ReadWritePair(ReadWritePair { ref style, .. }) => style.clone(),
+    }
+  }
+
+  /// Returns the target
+  #[inline]
+  pub fn target(&self) -> TermTarget {
+    self.inner.target.clone()
+  }
+
+  #[doc(hidden)]
+  pub fn write_str(&self, s: &str) -> io::Result<()> {
+    match self.inner.buffer {
+      Some(ref buffer) => buffer.lock().unwrap().write_all(s.as_bytes()),
+      None => self.write_through(s.as_bytes()),
+    }
+  }
+
+  /// Writes a string to the terminal and adds a newline.
+  pub fn write_line(&self, s: &str) -> io::Result<()> {
+    match self.inner.buffer {
+      Some(ref mutex) => {
+        let mut buffer = mutex.lock().unwrap();
+        buffer.extend_from_slice(s.as_bytes());
+        buffer.push(b'\n');
+        Ok(())
+      }
+      None => self.write_through(format!("{}\n", s).as_bytes()),
+    }
+  }
+
+  /// Read a single character from the terminal
+  ///
+  /// This does not echo the character and blocks until a single character
+  /// is entered.
+  pub fn read_char(&self) -> io::Result<char> {
+    loop {
+      match self.read_key()? {
+        Key::Char(c) => {
+          return Ok(c);
+        }
+        Key::Enter => {
+          return Ok('\n');
+        }
+        Key::Unknown => {
+          return Err(io::Error::new(
+            io::ErrorKind::NotConnected,
+            "Not a terminal",
+          ))
+        }
+        _ => {}
+      }
+    }
+  }
+
+  /// Read a single key form the terminal.
+  ///
+  /// This does not echo anything.  If the terminal is not user attended
+  /// the return value will always be the unknown key.
+  pub fn read_key(&self) -> io::Result<Key> {
+    if !self.is_tty {
+      Ok(Key::Unknown)
+    } else {
+      read_single_key()
+    }
+  }
+
+  /// Read one line of input.
+  ///
+  /// This does not include the trailing newline.  If the terminal is not
+  /// user attended the return value will always be an empty string.
+  pub fn read_line(&self) -> io::Result<String> {
+    if !self.is_tty {
+      return Ok("".into());
+    }
+    let mut rv = String::new();
+    io::stdin().read_line(&mut rv)?;
+    let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
+    rv.truncate(len);
+    Ok(rv)
+  }
+
+  /// Read one line of input with initial text.
+  ///
+  /// This does not include the trailing newline.  If the terminal is not
+  /// user attended the return value will always be an empty string.
+  pub fn read_line_initial_text(&self, initial: &str) -> io::Result<String> {
+    if !self.is_tty {
+      return Ok("".into());
+    }
+    self.write_str(initial)?;
+
+    let mut chars: Vec<char> = initial.chars().collect();
+
+    loop {
+      match self.read_key()? {
+        Key::Backspace => {
+          if chars.pop().is_some() {
+            self.clear_chars(1)?;
+          }
+          self.flush()?;
+        }
+        Key::Char(chr) => {
+          chars.push(chr);
+          let mut bytes_char = [0; 4];
+          chr.encode_utf8(&mut bytes_char);
+          self.write_str(chr.encode_utf8(&mut bytes_char))?;
+          self.flush()?;
+        }
+        Key::Enter => break,
+        Key::Unknown => {
+          return Err(io::Error::new(
+            io::ErrorKind::NotConnected,
+            "Not a terminal",
+          ))
+        }
+        _ => (),
+      }
+    }
+    Ok(chars.iter().collect::<String>())
+  }
+
+  /// Read securely a line of input.
+  ///
+  /// This is similar to `read_line` but will not echo the output.  This
+  /// also switches the terminal into a different mode where not all
+  /// characters might be accepted.
+  pub fn read_secure_line(&self) -> io::Result<String> {
+    if !self.is_tty {
+      return Ok("".into());
+    }
+    match read_secure() {
+      Ok(rv) => {
+        self.write_line("")?;
+        Ok(rv)
+      }
+      Err(err) => Err(err),
+    }
+  }
+
+  /// Flushes internal buffers.
+  ///
+  /// This forces the contents of the internal buffer to be written to
+  /// the terminal.  This is unnecessary for unbuffered terminals which
+  /// will automatically flush.
+  pub fn flush(&self) -> io::Result<()> {
+    if let Some(ref buffer) = self.inner.buffer {
+      let mut buffer = buffer.lock().unwrap();
+      if !buffer.is_empty() {
+        self.write_through(&buffer[..])?;
+        buffer.clear();
+      }
+    }
+    Ok(())
+  }
+
+  /// Checks if the terminal is indeed a terminal.
+  #[inline]
+  pub fn is_term(&self) -> bool {
+    self.is_tty
+  }
+
+  /// Checks for common terminal features.
+  #[inline]
+  pub fn features(&self) -> TermFeatures<'_> {
+    TermFeatures(self)
+  }
+
+  /// Returns the terminal size in rows and columns or gets sensible defaults.
+  #[inline]
+  pub fn size(&self) -> (u16, u16) {
+    self.size_checked().unwrap_or((24, DEFAULT_WIDTH))
+  }
+
+  /// Returns the terminal size in rows and columns.
+  ///
+  /// If the size cannot be reliably determined None is returned.
+  #[inline]
+  pub fn size_checked(&self) -> Option<(u16, u16)> {
+    terminal_size(self)
+  }
+
+  /// Moves the cursor to `x` and `y`
+  #[inline]
+  pub fn move_cursor_to(&self, x: usize, y: usize) -> io::Result<()> {
+    move_cursor_to(self, x, y)
+  }
+
+  /// Moves the cursor up `n` lines
+  #[inline]
+  pub fn move_cursor_up(&self, n: usize) -> io::Result<()> {
+    move_cursor_up(self, n)
+  }
+
+  /// Moves the cursor down `n` lines
+  #[inline]
+  pub fn move_cursor_down(&self, n: usize) -> io::Result<()> {
+    move_cursor_down(self, n)
+  }
+
+  /// Moves the cursor left `n` lines
+  #[inline]
+  pub fn move_cursor_left(&self, n: usize) -> io::Result<()> {
+    move_cursor_left(self, n)
+  }
+
+  /// Moves the cursor down `n` lines
+  #[inline]
+  pub fn move_cursor_right(&self, n: usize) -> io::Result<()> {
+    move_cursor_right(self, n)
+  }
+
+  /// Clears the current line.
+  ///
+  /// The positions the cursor at the beginning of the line again.
+  #[inline]
+  pub fn clear_line(&self) -> io::Result<()> {
+    clear_line(self)
+  }
+
+  /// Clear the last `n` lines.
+  ///
+  /// This positions the cursor at the beginning of the first line
+  /// that was cleared.
+  pub fn clear_last_lines(&self, n: usize) -> io::Result<()> {
+    self.move_cursor_up(n)?;
+    for _ in 0..n {
+      self.clear_line()?;
+      self.move_cursor_down(1)?;
+    }
+    self.move_cursor_up(n)?;
+    Ok(())
+  }
+
+  /// Clears the entire screen.
+  #[inline]
+  pub fn clear_screen(&self) -> io::Result<()> {
+    clear_screen(self)
+  }
+
+  /// Clears the entire screen.
+  #[inline]
+  pub fn clear_to_end_of_screen(&self) -> io::Result<()> {
+    clear_to_end_of_screen(self)
+  }
+
+  /// Clears the last char in the the current line.
+  #[inline]
+  pub fn clear_chars(&self, n: usize) -> io::Result<()> {
+    clear_chars(self, n)
+  }
+
+  /// Set the terminal title
+  pub fn set_title<T: Display>(&self, title: T) {
+    if !self.is_tty {
+      return;
+    }
+    set_title(title);
+  }
+
+  /// Makes cursor visible again
+  #[inline]
+  pub fn show_cursor(&self) -> io::Result<()> {
+    show_cursor(self)
+  }
+
+  /// Hides cursor
+  #[inline]
+  pub fn hide_cursor(&self) -> io::Result<()> {
+    hide_cursor(self)
+  }
+
+  // helpers
+
+  #[cfg(all(windows, feature = "windows-console-colors"))]
+  fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
+    if self.is_msys_tty || !self.is_tty {
+      self.write_through_common(bytes)
+    } else {
+      use winapi_util::console::Console;
+
+      match self.inner.target {
+        TermTarget::Stdout => console_colors(self, Console::stdout()?, bytes),
+        TermTarget::Stderr => console_colors(self, Console::stderr()?, bytes),
+      }
+    }
+  }
+
+  #[cfg(not(all(windows, feature = "windows-console-colors")))]
+  fn write_through(&self, bytes: &[u8]) -> io::Result<()> {
+    self.write_through_common(bytes)
+  }
+
+  pub(crate) fn write_through_common(&self, bytes: &[u8]) -> io::Result<()> {
+    match self.inner.target {
+      TermTarget::Stdout => {
+        io::stdout().write_all(bytes)?;
+        io::stdout().flush()?;
+      }
+      TermTarget::Stderr => {
+        io::stderr().write_all(bytes)?;
+        io::stderr().flush()?;
+      }
+      #[cfg(unix)]
+      TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
+        let mut write = write.lock().unwrap();
+        write.write_all(bytes)?;
+        write.flush()?;
+      }
+    }
+    Ok(())
+  }
+}
+
+/// A fast way to check if the application has a user attended for stdout.
+///
+/// This means that stdout is connected to a terminal instead of a
+/// file or redirected by other means. This is a shortcut for
+/// checking the `is_attended` feature on the stdout terminal.
+#[inline]
+pub fn user_attended() -> bool {
+  Term::stdout().features().is_attended()
+}
+
+/// A fast way to check if the application has a user attended for stderr.
+///
+/// This means that stderr is connected to a terminal instead of a
+/// file or redirected by other means. This is a shortcut for
+/// checking the `is_attended` feature on the stderr terminal.
+#[inline]
+pub fn user_attended_stderr() -> bool {
+  Term::stderr().features().is_attended()
+}
+
+#[cfg(unix)]
+impl AsRawFd for Term {
+  fn as_raw_fd(&self) -> RawFd {
+    match self.inner.target {
+      TermTarget::Stdout => libc::STDOUT_FILENO,
+      TermTarget::Stderr => libc::STDERR_FILENO,
+      TermTarget::ReadWritePair(ReadWritePair { ref write, .. }) => {
+        write.lock().unwrap().as_raw_fd()
+      }
+    }
+  }
+}
+
+#[cfg(windows)]
+impl AsRawHandle for Term {
+  fn as_raw_handle(&self) -> RawHandle {
+    use winapi::um::processenv::GetStdHandle;
+    use winapi::um::winbase::{STD_ERROR_HANDLE, STD_OUTPUT_HANDLE};
+
+    unsafe {
+      GetStdHandle(match self.inner.target {
+        TermTarget::Stdout => STD_OUTPUT_HANDLE,
+        TermTarget::Stderr => STD_ERROR_HANDLE,
+      }) as RawHandle
+    }
+  }
+}
+
+impl Write for Term {
+  fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+    match self.inner.buffer {
+      Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
+      None => self.write_through(buf),
+    }?;
+    Ok(buf.len())
+  }
+
+  fn flush(&mut self) -> io::Result<()> {
+    Term::flush(self)
+  }
+}
+
+impl<'a> Write for &'a Term {
+  fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+    match self.inner.buffer {
+      Some(ref buffer) => buffer.lock().unwrap().write_all(buf),
+      None => self.write_through(buf),
+    }?;
+    Ok(buf.len())
+  }
+
+  fn flush(&mut self) -> io::Result<()> {
+    Term::flush(self)
+  }
+}
+
+impl Read for Term {
+  fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+    io::stdin().read(buf)
+  }
+}
+
+impl<'a> Read for &'a Term {
+  fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+    io::stdin().read(buf)
+  }
+}
+
+#[cfg(unix)]
+pub use super::unix_term::*;
+#[cfg(target_arch = "wasm32")]
+pub use super::wasm_term::*;
+#[cfg(windows)]
+pub use super::windows_term::*;

+ 299 - 0
tooling/cli.rs/src/console/unix_term.rs

@@ -0,0 +1,299 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::env;
+use std::fmt::Display;
+use std::fs;
+use std::io;
+use std::io::{BufRead, BufReader};
+use std::os::unix::io::AsRawFd;
+use std::str;
+
+use super::kb::Key;
+use super::term::Term;
+
+pub use super::common_term::*;
+
+pub const DEFAULT_WIDTH: u16 = 80;
+
+#[inline]
+pub fn is_a_terminal(out: &Term) -> bool {
+  unsafe { libc::isatty(out.as_raw_fd()) != 0 }
+}
+
+pub fn is_a_color_terminal(out: &Term) -> bool {
+  if !is_a_terminal(out) {
+    return false;
+  }
+
+  if env::var("NO_COLOR").is_ok() {
+    return false;
+  }
+
+  match env::var("TERM") {
+    Ok(term) => term != "dumb",
+    Err(_) => false,
+  }
+}
+
+pub fn c_result<F: FnOnce() -> libc::c_int>(f: F) -> io::Result<()> {
+  let res = f();
+  if res != 0 {
+    Err(io::Error::last_os_error())
+  } else {
+    Ok(())
+  }
+}
+
+#[inline]
+pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {
+  terminal_size::terminal_size_using_fd(out.as_raw_fd()).map(|x| ((x.1).0, (x.0).0))
+}
+
+pub fn read_secure() -> io::Result<String> {
+  let f_tty;
+  let fd = unsafe {
+    if libc::isatty(libc::STDIN_FILENO) == 1 {
+      f_tty = None;
+      libc::STDIN_FILENO
+    } else {
+      let f = fs::File::open("/dev/tty")?;
+      let fd = f.as_raw_fd();
+      f_tty = Some(BufReader::new(f));
+      fd
+    }
+  };
+
+  let mut termios = core::mem::MaybeUninit::uninit();
+  c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
+  let mut termios = unsafe { termios.assume_init() };
+  let original = termios;
+  termios.c_lflag &= !libc::ECHO;
+  c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &termios) })?;
+  let mut rv = String::new();
+
+  let read_rv = if let Some(mut f) = f_tty {
+    f.read_line(&mut rv)
+  } else {
+    io::stdin().read_line(&mut rv)
+  };
+
+  c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSAFLUSH, &original) })?;
+
+  read_rv.map(|_| {
+    let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
+    rv.truncate(len);
+    rv
+  })
+}
+
+fn read_single_char(fd: i32) -> io::Result<Option<char>> {
+  let mut pollfd = libc::pollfd {
+    fd,
+    events: libc::POLLIN,
+    revents: 0,
+  };
+
+  // timeout of zero means that it will not block
+  let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, 0) };
+  if ret < 0 {
+    return Err(io::Error::last_os_error());
+  }
+
+  let is_ready = pollfd.revents & libc::POLLIN != 0;
+
+  if is_ready {
+    // if there is something to be read, take 1 byte from it
+    let mut buf: [u8; 1] = [0];
+
+    read_bytes(fd, &mut buf, 1)?;
+    Ok(Some(buf[0] as char))
+  } else {
+    //there is nothing to be read
+    Ok(None)
+  }
+}
+
+// Similar to libc::read. Read count bytes into slice buf from descriptor fd.
+// If successful, return the number of bytes read.
+// Will return an error if nothing was read, i.e when called at end of file.
+fn read_bytes(fd: i32, buf: &mut [u8], count: u8) -> io::Result<u8> {
+  let read = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, count as usize) };
+  if read < 0 {
+    Err(io::Error::last_os_error())
+  } else if read == 0 {
+    Err(io::Error::new(
+      io::ErrorKind::UnexpectedEof,
+      "Reached end of file",
+    ))
+  } else if buf[0] == b'\x03' {
+    Err(io::Error::new(
+      io::ErrorKind::Interrupted,
+      "read interrupted",
+    ))
+  } else {
+    Ok(read as u8)
+  }
+}
+
+pub fn read_single_key() -> io::Result<Key> {
+  let tty_f;
+  let fd = unsafe {
+    if libc::isatty(libc::STDIN_FILENO) == 1 {
+      libc::STDIN_FILENO
+    } else {
+      tty_f = fs::File::open("/dev/tty")?;
+      tty_f.as_raw_fd()
+    }
+  };
+  let mut termios = core::mem::MaybeUninit::uninit();
+  c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
+  let mut termios = unsafe { termios.assume_init() };
+  let original = termios;
+  unsafe { libc::cfmakeraw(&mut termios) };
+  c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &termios) })?;
+
+  let rv = match read_single_char(fd)? {
+    Some('\x1b') => {
+      // Escape was read, keep reading in case we find a familiar key
+      if let Some(c1) = read_single_char(fd)? {
+        if c1 == '[' {
+          if let Some(c2) = read_single_char(fd)? {
+            match c2 {
+              'A' => Ok(Key::ArrowUp),
+              'B' => Ok(Key::ArrowDown),
+              'C' => Ok(Key::ArrowRight),
+              'D' => Ok(Key::ArrowLeft),
+              'H' => Ok(Key::Home),
+              'F' => Ok(Key::End),
+              'Z' => Ok(Key::BackTab),
+              _ => {
+                let c3 = read_single_char(fd)?;
+                if let Some(c3) = c3 {
+                  if c3 == '~' {
+                    match c2 {
+                      '1' => Ok(Key::Home), // tmux
+                      '2' => Ok(Key::Insert),
+                      '3' => Ok(Key::Del),
+                      '4' => Ok(Key::End), // tmux
+                      '5' => Ok(Key::PageUp),
+                      '6' => Ok(Key::PageDown),
+                      '7' => Ok(Key::Home), // xrvt
+                      '8' => Ok(Key::End),  // xrvt
+                      _ => Ok(Key::UnknownEscSeq(vec![c1, c2, c3])),
+                    }
+                  } else {
+                    Ok(Key::UnknownEscSeq(vec![c1, c2, c3]))
+                  }
+                } else {
+                  // \x1b[ and 1 more char
+                  Ok(Key::UnknownEscSeq(vec![c1, c2]))
+                }
+              }
+            }
+          } else {
+            // \x1b[ and no more input
+            Ok(Key::UnknownEscSeq(vec![c1]))
+          }
+        } else {
+          // char after escape is not [
+          Ok(Key::UnknownEscSeq(vec![c1]))
+        }
+      } else {
+        //nothing after escape
+        Ok(Key::Escape)
+      }
+    }
+    Some(c) => {
+      let byte = c as u8;
+      let mut buf: [u8; 4] = [byte, 0, 0, 0];
+
+      if byte & 224u8 == 192u8 {
+        // a two byte unicode character
+        read_bytes(fd, &mut buf[1..], 1)?;
+        Ok(key_from_utf8(&buf[..2]))
+      } else if byte & 240u8 == 224u8 {
+        // a three byte unicode character
+        read_bytes(fd, &mut buf[1..], 2)?;
+        Ok(key_from_utf8(&buf[..3]))
+      } else if byte & 248u8 == 240u8 {
+        // a four byte unicode character
+        read_bytes(fd, &mut buf[1..], 3)?;
+        Ok(key_from_utf8(&buf[..4]))
+      } else {
+        Ok(match c {
+          '\n' | '\r' => Key::Enter,
+          '\x7f' => Key::Backspace,
+          '\t' => Key::Tab,
+          '\x01' => Key::Home,      // Control-A (home)
+          '\x05' => Key::End,       // Control-E (end)
+          '\x08' => Key::Backspace, // Control-H (8) (Identical to '\b')
+          _ => Key::Char(c),
+        })
+      }
+    }
+    None => {
+      // there is no subsequent byte ready to be read, block and wait for input
+
+      let mut pollfd = libc::pollfd {
+        fd,
+        events: libc::POLLIN,
+        revents: 0,
+      };
+
+      // negative timeout means that it will block indefinitely
+      let ret = unsafe { libc::poll(&mut pollfd as *mut _, 1, -1) };
+      if ret < 0 {
+        return Err(io::Error::last_os_error());
+      }
+
+      read_single_key()
+    }
+  };
+  c_result(|| unsafe { libc::tcsetattr(fd, libc::TCSADRAIN, &original) })?;
+
+  // if the user hit ^C we want to signal SIGINT to outselves.
+  if let Err(ref err) = rv {
+    if err.kind() == io::ErrorKind::Interrupted {
+      unsafe {
+        libc::raise(libc::SIGINT);
+      }
+    }
+  }
+
+  rv
+}
+
+pub fn key_from_utf8(buf: &[u8]) -> Key {
+  if let Ok(s) = str::from_utf8(buf) {
+    if let Some(c) = s.chars().next() {
+      return Key::Char(c);
+    }
+  }
+  Key::Unknown
+}
+
+#[cfg(not(target_os = "macos"))]
+lazy_static::lazy_static! {
+    static ref IS_LANG_UTF8: bool = {
+        match std::env::var("LANG") {
+            Ok(lang) => lang.to_uppercase().ends_with("UTF-8"),
+            _ => false,
+        }
+    };
+}
+
+#[cfg(target_os = "macos")]
+pub fn wants_emoji() -> bool {
+  true
+}
+
+#[cfg(not(target_os = "macos"))]
+pub fn wants_emoji() -> bool {
+  *IS_LANG_UTF8
+}
+
+pub fn set_title<T: Display>(title: T) {
+  print!("\x1b]0;{}\x07", title);
+}

+ 900 - 0
tooling/cli.rs/src/console/utils.rs

@@ -0,0 +1,900 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::borrow::Cow;
+use std::collections::BTreeSet;
+use std::env;
+use std::fmt;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+use super::term::{wants_emoji, Term};
+use lazy_static::lazy_static;
+
+use super::ansi::{strip_ansi_codes, AnsiCodeIterator};
+
+fn default_colors_enabled(out: &Term) -> bool {
+  (out.features().colors_supported() && &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0")
+    || &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0"
+}
+
+lazy_static! {
+  static ref STDOUT_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stdout()));
+  static ref STDERR_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stderr()));
+}
+
+/// Returns `true` if colors should be enabled for stdout.
+///
+/// This honors the [clicolors spec](http://bixense.com/clicolors/).
+///
+/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
+/// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
+/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
+#[inline]
+pub fn colors_enabled() -> bool {
+  STDOUT_COLORS.load(Ordering::Relaxed)
+}
+
+/// Forces colorization on or off for stdout.
+///
+/// This overrides the default for the current process and changes the return value of the
+/// `colors_enabled` function.
+#[inline]
+pub fn set_colors_enabled(val: bool) {
+  STDOUT_COLORS.store(val, Ordering::Relaxed)
+}
+
+/// Returns `true` if colors should be enabled for stderr.
+///
+/// This honors the [clicolors spec](http://bixense.com/clicolors/).
+///
+/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
+/// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
+/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
+#[inline]
+pub fn colors_enabled_stderr() -> bool {
+  STDERR_COLORS.load(Ordering::Relaxed)
+}
+
+/// Forces colorization on or off for stderr.
+///
+/// This overrides the default for the current process and changes the return value of the
+/// `colors_enabled` function.
+#[inline]
+pub fn set_colors_enabled_stderr(val: bool) {
+  STDERR_COLORS.store(val, Ordering::Relaxed)
+}
+
+/// Measure the width of a string in terminal characters.
+pub fn measure_text_width(s: &str) -> usize {
+  str_width(&strip_ansi_codes(s))
+}
+
+/// A terminal color.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Color {
+  Black,
+  Red,
+  Green,
+  Yellow,
+  Blue,
+  Magenta,
+  Cyan,
+  White,
+  Color256(u8),
+}
+
+impl Color {
+  #[inline]
+  fn ansi_num(self) -> usize {
+    match self {
+      Color::Black => 0,
+      Color::Red => 1,
+      Color::Green => 2,
+      Color::Yellow => 3,
+      Color::Blue => 4,
+      Color::Magenta => 5,
+      Color::Cyan => 6,
+      Color::White => 7,
+      Color::Color256(x) => x as usize,
+    }
+  }
+
+  #[inline]
+  fn is_color256(self) -> bool {
+    #[allow(clippy::match_like_matches_macro)]
+    match self {
+      Color::Color256(_) => true,
+      _ => false,
+    }
+  }
+}
+
+/// A terminal style attribute.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
+pub enum Attribute {
+  Bold,
+  Dim,
+  Italic,
+  Underlined,
+  Blink,
+  Reverse,
+  Hidden,
+}
+
+impl Attribute {
+  #[inline]
+  fn ansi_num(self) -> usize {
+    match self {
+      Attribute::Bold => 1,
+      Attribute::Dim => 2,
+      Attribute::Italic => 3,
+      Attribute::Underlined => 4,
+      Attribute::Blink => 5,
+      Attribute::Reverse => 7,
+      Attribute::Hidden => 8,
+    }
+  }
+}
+
+/// Defines the alignment for padding operations.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Alignment {
+  Left,
+  Center,
+  Right,
+}
+
+/// A stored style that can be applied.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Style {
+  fg: Option<Color>,
+  bg: Option<Color>,
+  fg_bright: bool,
+  bg_bright: bool,
+  attrs: BTreeSet<Attribute>,
+  force: Option<bool>,
+  for_stderr: bool,
+}
+
+impl Default for Style {
+  fn default() -> Style {
+    Style::new()
+  }
+}
+
+impl Style {
+  /// Returns an empty default style.
+  pub fn new() -> Style {
+    Style {
+      fg: None,
+      bg: None,
+      fg_bright: false,
+      bg_bright: false,
+      attrs: BTreeSet::new(),
+      force: None,
+      for_stderr: false,
+    }
+  }
+
+  /// Creates a style from a dotted string.
+  ///
+  /// Effectively the string is split at each dot and then the
+  /// terms in between are applied.  For instance `red.on_blue` will
+  /// create a string that is red on blue background.  Unknown terms
+  /// are ignored.
+  pub fn from_dotted_str(s: &str) -> Style {
+    let mut rv = Style::new();
+    for part in s.split('.') {
+      rv = match part {
+        "black" => rv.black(),
+        "red" => rv.red(),
+        "green" => rv.green(),
+        "yellow" => rv.yellow(),
+        "blue" => rv.blue(),
+        "magenta" => rv.magenta(),
+        "cyan" => rv.cyan(),
+        "white" => rv.white(),
+        "bright" => rv.bright(),
+        "on_black" => rv.on_black(),
+        "on_red" => rv.on_red(),
+        "on_green" => rv.on_green(),
+        "on_yellow" => rv.on_yellow(),
+        "on_blue" => rv.on_blue(),
+        "on_magenta" => rv.on_magenta(),
+        "on_cyan" => rv.on_cyan(),
+        "on_white" => rv.on_white(),
+        "on_bright" => rv.on_bright(),
+        "bold" => rv.bold(),
+        "dim" => rv.dim(),
+        "underlined" => rv.underlined(),
+        "blink" => rv.blink(),
+        "reverse" => rv.reverse(),
+        "hidden" => rv.hidden(),
+        _ => {
+          continue;
+        }
+      };
+    }
+    rv
+  }
+
+  /// Apply the style to something that can be displayed.
+  pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
+    StyledObject {
+      style: self.clone(),
+      val,
+    }
+  }
+
+  /// Forces styling on or off.
+  ///
+  /// This overrides the detection from `clicolors-control`.
+  #[inline]
+  pub fn force_styling(mut self, value: bool) -> Style {
+    self.force = Some(value);
+    self
+  }
+
+  /// Specifies that style is applying to something being written on stderr.
+  #[inline]
+  pub fn for_stderr(mut self) -> Style {
+    self.for_stderr = true;
+    self
+  }
+
+  /// Specifies that style is applying to something being written on stdout.
+  ///
+  /// This is the default behaviour.
+  #[inline]
+  pub fn for_stdout(mut self) -> Style {
+    self.for_stderr = false;
+    self
+  }
+
+  /// Sets a foreground color.
+  #[inline]
+  pub fn fg(mut self, color: Color) -> Style {
+    self.fg = Some(color);
+    self
+  }
+
+  /// Sets a background color.
+  #[inline]
+  pub fn bg(mut self, color: Color) -> Style {
+    self.bg = Some(color);
+    self
+  }
+
+  /// Adds a attr.
+  #[inline]
+  pub fn attr(mut self, attr: Attribute) -> Style {
+    self.attrs.insert(attr);
+    self
+  }
+
+  #[inline]
+  pub fn black(self) -> Style {
+    self.fg(Color::Black)
+  }
+  #[inline]
+  pub fn red(self) -> Style {
+    self.fg(Color::Red)
+  }
+  #[inline]
+  pub fn green(self) -> Style {
+    self.fg(Color::Green)
+  }
+  #[inline]
+  pub fn yellow(self) -> Style {
+    self.fg(Color::Yellow)
+  }
+  #[inline]
+  pub fn blue(self) -> Style {
+    self.fg(Color::Blue)
+  }
+  #[inline]
+  pub fn magenta(self) -> Style {
+    self.fg(Color::Magenta)
+  }
+  #[inline]
+  pub fn cyan(self) -> Style {
+    self.fg(Color::Cyan)
+  }
+  #[inline]
+  pub fn white(self) -> Style {
+    self.fg(Color::White)
+  }
+  #[inline]
+  pub fn color256(self, color: u8) -> Style {
+    self.fg(Color::Color256(color))
+  }
+
+  #[inline]
+  pub fn bright(mut self) -> Style {
+    self.fg_bright = true;
+    self
+  }
+
+  #[inline]
+  pub fn on_black(self) -> Style {
+    self.bg(Color::Black)
+  }
+  #[inline]
+  pub fn on_red(self) -> Style {
+    self.bg(Color::Red)
+  }
+  #[inline]
+  pub fn on_green(self) -> Style {
+    self.bg(Color::Green)
+  }
+  #[inline]
+  pub fn on_yellow(self) -> Style {
+    self.bg(Color::Yellow)
+  }
+  #[inline]
+  pub fn on_blue(self) -> Style {
+    self.bg(Color::Blue)
+  }
+  #[inline]
+  pub fn on_magenta(self) -> Style {
+    self.bg(Color::Magenta)
+  }
+  #[inline]
+  pub fn on_cyan(self) -> Style {
+    self.bg(Color::Cyan)
+  }
+  #[inline]
+  pub fn on_white(self) -> Style {
+    self.bg(Color::White)
+  }
+  #[inline]
+  pub fn on_color256(self, color: u8) -> Style {
+    self.bg(Color::Color256(color))
+  }
+
+  #[inline]
+  pub fn on_bright(mut self) -> Style {
+    self.bg_bright = true;
+    self
+  }
+
+  #[inline]
+  pub fn bold(self) -> Style {
+    self.attr(Attribute::Bold)
+  }
+  #[inline]
+  pub fn dim(self) -> Style {
+    self.attr(Attribute::Dim)
+  }
+  #[inline]
+  pub fn italic(self) -> Style {
+    self.attr(Attribute::Italic)
+  }
+  #[inline]
+  pub fn underlined(self) -> Style {
+    self.attr(Attribute::Underlined)
+  }
+  #[inline]
+  pub fn blink(self) -> Style {
+    self.attr(Attribute::Blink)
+  }
+  #[inline]
+  pub fn reverse(self) -> Style {
+    self.attr(Attribute::Reverse)
+  }
+  #[inline]
+  pub fn hidden(self) -> Style {
+    self.attr(Attribute::Hidden)
+  }
+}
+
+/// Wraps an object for formatting for styling.
+///
+/// Example:
+///
+/// ```rust,no_run
+/// # use console::style;
+/// format!("Hello {}", style("World").cyan());
+/// ```
+///
+/// This is a shortcut for making a new style and applying it
+/// to a value:
+///
+/// ```rust,no_run
+/// # use console::Style;
+/// format!("Hello {}", Style::new().cyan().apply_to("World"));
+/// ```
+pub fn style<D>(val: D) -> StyledObject<D> {
+  Style::new().apply_to(val)
+}
+
+/// A formatting wrapper that can be styled for a terminal.
+#[derive(Clone)]
+pub struct StyledObject<D> {
+  style: Style,
+  val: D,
+}
+
+impl<D> StyledObject<D> {
+  /// Forces styling on or off.
+  ///
+  /// This overrides the detection from `clicolors-control`.
+  #[inline]
+  pub fn force_styling(mut self, value: bool) -> StyledObject<D> {
+    self.style = self.style.force_styling(value);
+    self
+  }
+
+  /// Specifies that style is applying to something being written on stderr
+  #[inline]
+  pub fn for_stderr(mut self) -> StyledObject<D> {
+    self.style = self.style.for_stderr();
+    self
+  }
+
+  /// Specifies that style is applying to something being written on stdout
+  ///
+  /// This is the default
+  #[inline]
+  pub fn for_stdout(mut self) -> StyledObject<D> {
+    self.style = self.style.for_stdout();
+    self
+  }
+
+  /// Sets a foreground color.
+  #[inline]
+  pub fn fg(mut self, color: Color) -> StyledObject<D> {
+    self.style = self.style.fg(color);
+    self
+  }
+
+  /// Sets a background color.
+  #[inline]
+  pub fn bg(mut self, color: Color) -> StyledObject<D> {
+    self.style = self.style.bg(color);
+    self
+  }
+
+  /// Adds a attr.
+  #[inline]
+  pub fn attr(mut self, attr: Attribute) -> StyledObject<D> {
+    self.style = self.style.attr(attr);
+    self
+  }
+
+  #[inline]
+  pub fn black(self) -> StyledObject<D> {
+    self.fg(Color::Black)
+  }
+  #[inline]
+  pub fn red(self) -> StyledObject<D> {
+    self.fg(Color::Red)
+  }
+  #[inline]
+  pub fn green(self) -> StyledObject<D> {
+    self.fg(Color::Green)
+  }
+  #[inline]
+  pub fn yellow(self) -> StyledObject<D> {
+    self.fg(Color::Yellow)
+  }
+  #[inline]
+  pub fn blue(self) -> StyledObject<D> {
+    self.fg(Color::Blue)
+  }
+  #[inline]
+  pub fn magenta(self) -> StyledObject<D> {
+    self.fg(Color::Magenta)
+  }
+  #[inline]
+  pub fn cyan(self) -> StyledObject<D> {
+    self.fg(Color::Cyan)
+  }
+  #[inline]
+  pub fn white(self) -> StyledObject<D> {
+    self.fg(Color::White)
+  }
+  #[inline]
+  pub fn color256(self, color: u8) -> StyledObject<D> {
+    self.fg(Color::Color256(color))
+  }
+
+  #[inline]
+  pub fn bright(mut self) -> StyledObject<D> {
+    self.style = self.style.bright();
+    self
+  }
+
+  #[inline]
+  pub fn on_black(self) -> StyledObject<D> {
+    self.bg(Color::Black)
+  }
+  #[inline]
+  pub fn on_red(self) -> StyledObject<D> {
+    self.bg(Color::Red)
+  }
+  #[inline]
+  pub fn on_green(self) -> StyledObject<D> {
+    self.bg(Color::Green)
+  }
+  #[inline]
+  pub fn on_yellow(self) -> StyledObject<D> {
+    self.bg(Color::Yellow)
+  }
+  #[inline]
+  pub fn on_blue(self) -> StyledObject<D> {
+    self.bg(Color::Blue)
+  }
+  #[inline]
+  pub fn on_magenta(self) -> StyledObject<D> {
+    self.bg(Color::Magenta)
+  }
+  #[inline]
+  pub fn on_cyan(self) -> StyledObject<D> {
+    self.bg(Color::Cyan)
+  }
+  #[inline]
+  pub fn on_white(self) -> StyledObject<D> {
+    self.bg(Color::White)
+  }
+  #[inline]
+  pub fn on_color256(self, color: u8) -> StyledObject<D> {
+    self.bg(Color::Color256(color))
+  }
+
+  #[inline]
+  pub fn on_bright(mut self) -> StyledObject<D> {
+    self.style = self.style.on_bright();
+    self
+  }
+
+  #[inline]
+  pub fn bold(self) -> StyledObject<D> {
+    self.attr(Attribute::Bold)
+  }
+  #[inline]
+  pub fn dim(self) -> StyledObject<D> {
+    self.attr(Attribute::Dim)
+  }
+  #[inline]
+  pub fn italic(self) -> StyledObject<D> {
+    self.attr(Attribute::Italic)
+  }
+  #[inline]
+  pub fn underlined(self) -> StyledObject<D> {
+    self.attr(Attribute::Underlined)
+  }
+  #[inline]
+  pub fn blink(self) -> StyledObject<D> {
+    self.attr(Attribute::Blink)
+  }
+  #[inline]
+  pub fn reverse(self) -> StyledObject<D> {
+    self.attr(Attribute::Reverse)
+  }
+  #[inline]
+  pub fn hidden(self) -> StyledObject<D> {
+    self.attr(Attribute::Hidden)
+  }
+}
+
+macro_rules! impl_fmt {
+  ($name:ident) => {
+    impl<D: fmt::$name> fmt::$name for StyledObject<D> {
+      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let mut reset = false;
+        if self
+          .style
+          .force
+          .unwrap_or_else(|| match self.style.for_stderr {
+            true => colors_enabled_stderr(),
+            false => colors_enabled(),
+          })
+        {
+          if let Some(fg) = self.style.fg {
+            if fg.is_color256() {
+              write!(f, "\x1b[38;5;{}m", fg.ansi_num())?;
+            } else if self.style.fg_bright {
+              write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?;
+            } else {
+              write!(f, "\x1b[{}m", fg.ansi_num() + 30)?;
+            }
+            reset = true;
+          }
+          if let Some(bg) = self.style.bg {
+            if bg.is_color256() {
+              write!(f, "\x1b[48;5;{}m", bg.ansi_num())?;
+            } else if self.style.bg_bright {
+              write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?;
+            } else {
+              write!(f, "\x1b[{}m", bg.ansi_num() + 40)?;
+            }
+            reset = true;
+          }
+          for attr in &self.style.attrs {
+            write!(f, "\x1b[{}m", attr.ansi_num())?;
+            reset = true;
+          }
+        }
+        fmt::$name::fmt(&self.val, f)?;
+        if reset {
+          write!(f, "\x1b[0m")?;
+        }
+        Ok(())
+      }
+    }
+  };
+}
+
+impl_fmt!(Binary);
+impl_fmt!(Debug);
+impl_fmt!(Display);
+impl_fmt!(LowerExp);
+impl_fmt!(LowerHex);
+impl_fmt!(Octal);
+impl_fmt!(Pointer);
+impl_fmt!(UpperExp);
+impl_fmt!(UpperHex);
+
+/// "Intelligent" emoji formatter.
+///
+/// This struct intelligently wraps an emoji so that it is rendered
+/// only on systems that want emojis and renders a fallback on others.
+///
+/// Example:
+///
+/// ```rust
+/// use console::Emoji;
+/// println!("[3/4] {}Downloading ...", Emoji("🚚 ", ""));
+/// println!("[4/4] {} Done!", Emoji("✨", ":-)"));
+/// ```
+#[derive(Copy, Clone)]
+pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str);
+
+impl<'a, 'b> Emoji<'a, 'b> {
+  pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> {
+    Emoji(emoji, fallback)
+  }
+}
+
+impl<'a, 'b> fmt::Display for Emoji<'a, 'b> {
+  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    if wants_emoji() {
+      write!(f, "{}", self.0)
+    } else {
+      write!(f, "{}", self.1)
+    }
+  }
+}
+
+fn str_width(s: &str) -> usize {
+  #[cfg(feature = "unicode-width")]
+  {
+    use unicode_width::UnicodeWidthStr;
+    s.width()
+  }
+  #[cfg(not(feature = "unicode-width"))]
+  {
+    s.chars().count()
+  }
+}
+
+fn char_width(c: char) -> usize {
+  #[cfg(feature = "unicode-width")]
+  {
+    use unicode_width::UnicodeWidthChar;
+    c.width().unwrap_or(0)
+  }
+  #[cfg(not(feature = "unicode-width"))]
+  {
+    let _c = c;
+    1
+  }
+}
+
+/// Truncates a string to a certain number of characters.
+///
+/// This ensures that escape codes are not screwed up in the process.
+/// If the maximum length is hit the string will be truncated but
+/// escapes code will still be honored.  If truncation takes place
+/// the tail string will be appended.
+pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
+  {
+    use std::cmp::Ordering;
+    let mut iter = AnsiCodeIterator::new(s);
+    let mut length = 0;
+    let mut rv = None;
+
+    while let Some(item) = iter.next() {
+      match item {
+        (s, false) => {
+          if rv.is_none() {
+            if str_width(s) + length > width - str_width(tail) {
+              let ts = iter.current_slice();
+
+              let mut s_byte = 0;
+              let mut s_width = 0;
+              let rest_width = width - str_width(tail) - length;
+              for c in s.chars() {
+                s_byte += c.len_utf8();
+                s_width += char_width(c);
+                match s_width.cmp(&rest_width) {
+                  Ordering::Equal => break,
+                  Ordering::Greater => {
+                    s_byte -= c.len_utf8();
+                    break;
+                  }
+                  Ordering::Less => continue,
+                }
+              }
+
+              let idx = ts.len() - s.len() + s_byte;
+              let mut buf = ts[..idx].to_string();
+              buf.push_str(tail);
+              rv = Some(buf);
+            }
+            length += str_width(s);
+          }
+        }
+        (s, true) => {
+          if rv.is_some() {
+            rv.as_mut().unwrap().push_str(s);
+          }
+        }
+      }
+    }
+
+    if let Some(buf) = rv {
+      Cow::Owned(buf)
+    } else {
+      Cow::Borrowed(s)
+    }
+  }
+}
+
+/// Pads a string to fill a certain number of characters.
+///
+/// This will honor ansi codes correctly and allows you to align a string
+/// on the left, right or centered.  Additionally truncation can be enabled
+/// by setting `truncate` to a string that should be used as a truncation
+/// marker.
+pub fn pad_str<'a>(
+  s: &'a str,
+  width: usize,
+  align: Alignment,
+  truncate: Option<&str>,
+) -> Cow<'a, str> {
+  pad_str_with(s, width, align, truncate, ' ')
+}
+/// Pads a string with specific padding to fill a certain number of characters.
+///
+/// This will honor ansi codes correctly and allows you to align a string
+/// on the left, right or centered.  Additionally truncation can be enabled
+/// by setting `truncate` to a string that should be used as a truncation
+/// marker.
+pub fn pad_str_with<'a>(
+  s: &'a str,
+  width: usize,
+  align: Alignment,
+  truncate: Option<&str>,
+  pad: char,
+) -> Cow<'a, str> {
+  let cols = measure_text_width(s);
+
+  if cols >= width {
+    return match truncate {
+      None => Cow::Borrowed(s),
+      Some(tail) => truncate_str(s, width, tail),
+    };
+  }
+
+  let diff = width - cols;
+
+  let (left_pad, right_pad) = match align {
+    Alignment::Left => (0, diff),
+    Alignment::Right => (diff, 0),
+    Alignment::Center => (diff / 2, diff - diff / 2),
+  };
+
+  let mut rv = String::new();
+  for _ in 0..left_pad {
+    rv.push(pad);
+  }
+  rv.push_str(s);
+  for _ in 0..right_pad {
+    rv.push(pad);
+  }
+  Cow::Owned(rv)
+}
+
+#[test]
+fn test_text_width() {
+  let s = style("foo")
+    .red()
+    .on_black()
+    .bold()
+    .force_styling(true)
+    .to_string();
+  assert_eq!(measure_text_width(&s), 3);
+}
+
+#[test]
+#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
+fn test_truncate_str() {
+  let s = format!("foo {}", style("bar").red().force_styling(true));
+  assert_eq!(
+    &truncate_str(&s, 5, ""),
+    &format!("foo {}", style("b").red().force_styling(true))
+  );
+  let s = format!("foo {}", style("bar").red().force_styling(true));
+  assert_eq!(
+    &truncate_str(&s, 5, "!"),
+    &format!("foo {}", style("!").red().force_styling(true))
+  );
+  let s = format!("foo {} baz", style("bar").red().force_styling(true));
+  assert_eq!(
+    &truncate_str(&s, 10, "..."),
+    &format!("foo {}...", style("bar").red().force_styling(true))
+  );
+  let s = format!("foo {}", style("バー").red().force_styling(true));
+  assert_eq!(
+    &truncate_str(&s, 5, ""),
+    &format!("foo {}", style("").red().force_styling(true))
+  );
+  let s = format!("foo {}", style("バー").red().force_styling(true));
+  assert_eq!(
+    &truncate_str(&s, 6, ""),
+    &format!("foo {}", style("バ").red().force_styling(true))
+  );
+}
+
+#[test]
+fn test_truncate_str_no_ansi() {
+  assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
+  assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
+  assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
+}
+
+#[test]
+fn test_pad_str() {
+  assert_eq!(pad_str("foo", 7, Alignment::Center, None), "  foo  ");
+  assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo    ");
+  assert_eq!(pad_str("foo", 7, Alignment::Right, None), "    foo");
+  assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
+  assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
+  assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
+  assert_eq!(
+    pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
+    "foo..."
+  );
+}
+
+#[test]
+fn test_pad_str_with() {
+  assert_eq!(
+    pad_str_with("foo", 7, Alignment::Center, None, '#'),
+    "##foo##"
+  );
+  assert_eq!(
+    pad_str_with("foo", 7, Alignment::Left, None, '#'),
+    "foo####"
+  );
+  assert_eq!(
+    pad_str_with("foo", 7, Alignment::Right, None, '#'),
+    "####foo"
+  );
+  assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
+  assert_eq!(
+    pad_str_with("foobar", 3, Alignment::Left, None, '#'),
+    "foobar"
+  );
+  assert_eq!(
+    pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
+    "foo"
+  );
+  assert_eq!(
+    pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
+    "foo..."
+  );
+}

+ 49 - 0
tooling/cli.rs/src/console/wasm_term.rs

@@ -0,0 +1,49 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::fmt::Display;
+use std::io;
+
+use super::kb::Key;
+use super::term::Term;
+
+pub use super::common_term::*;
+
+pub const DEFAULT_WIDTH: u16 = 80;
+
+#[inline]
+pub fn is_a_terminal(_out: &Term) -> bool {
+  false
+}
+
+#[inline]
+pub fn is_a_color_terminal(_out: &Term) -> bool {
+  false
+}
+
+#[inline]
+pub fn terminal_size(_out: &Term) -> Option<(u16, u16)> {
+  None
+}
+
+pub fn read_secure() -> io::Result<String> {
+  Err(io::Error::new(
+    io::ErrorKind::Other,
+    "unsupported operation",
+  ))
+}
+
+pub fn read_single_key() -> io::Result<Key> {
+  Err(io::Error::new(
+    io::ErrorKind::Other,
+    "unsupported operation",
+  ))
+}
+
+#[inline]
+pub fn wants_emoji() -> bool {
+  false
+}
+
+pub fn set_title<T: Display>(_title: T) {}

+ 600 - 0
tooling/cli.rs/src/console/windows_term.rs

@@ -0,0 +1,600 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::cmp;
+use std::env;
+use std::ffi::OsStr;
+use std::fmt::Display;
+use std::io;
+use std::iter::once;
+use std::mem;
+use std::os::windows::ffi::OsStrExt;
+use std::os::windows::io::AsRawHandle;
+use std::slice;
+use std::{char, mem::MaybeUninit};
+
+use encode_unicode::error::InvalidUtf16Tuple;
+use encode_unicode::CharExt;
+#[cfg(feature = "windows-console-colors")]
+use regex::Regex;
+use winapi::ctypes::c_void;
+use winapi::shared::minwindef::DWORD;
+use winapi::shared::minwindef::MAX_PATH;
+use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
+use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, ReadConsoleInputW};
+use winapi::um::fileapi::FILE_NAME_INFO;
+use winapi::um::handleapi::INVALID_HANDLE_VALUE;
+use winapi::um::minwinbase::FileNameInfo;
+use winapi::um::processenv::GetStdHandle;
+use winapi::um::winbase::GetFileInformationByHandleEx;
+use winapi::um::winbase::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE};
+use winapi::um::wincon::{
+  FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetConsoleCursorInfo,
+  GetConsoleScreenBufferInfo, SetConsoleCursorInfo, SetConsoleCursorPosition, SetConsoleTitleW,
+  CONSOLE_CURSOR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, KEY_EVENT,
+  KEY_EVENT_RECORD,
+};
+use winapi::um::winnt::{CHAR, HANDLE, INT, WCHAR};
+#[cfg(feature = "windows-console-colors")]
+use winapi_util::console::{Color, Console, Intense};
+
+use super::common_term;
+use super::kb::Key;
+use super::term::{Term, TermTarget};
+
+#[cfg(feature = "windows-console-colors")]
+lazy_static::lazy_static! {
+    static ref INTENSE_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)8;5;(8|9|1[0-5])m").unwrap();
+    static ref NORMAL_COLOR_RE: Regex = Regex::new(r"\x1b\[(3|4)([0-7])m").unwrap();
+    static ref ATTR_RE: Regex = Regex::new(r"\x1b\[([1-8])m").unwrap();
+}
+
+const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x4;
+pub const DEFAULT_WIDTH: u16 = 79;
+
+pub fn as_handle(term: &Term) -> HANDLE {
+  // convert between winapi::um::winnt::HANDLE and std::os::windows::raw::HANDLE
+  // which are both c_void. would be nice to find a better way to do this
+  term.as_raw_handle() as HANDLE
+}
+
+pub fn is_a_terminal(out: &Term) -> bool {
+  let (fd, others) = match out.target() {
+    TermTarget::Stdout => (STD_OUTPUT_HANDLE, [STD_INPUT_HANDLE, STD_ERROR_HANDLE]),
+    TermTarget::Stderr => (STD_ERROR_HANDLE, [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE]),
+  };
+
+  if unsafe { console_on_any(&[fd]) } {
+    // False positives aren't possible. If we got a console then
+    // we definitely have a tty on stdin.
+    return true;
+  }
+
+  // At this point, we *could* have a false negative. We can determine that
+  // this is true negative if we can detect the presence of a console on
+  // any of the other streams. If another stream has a console, then we know
+  // we're in a Windows console and can therefore trust the negative.
+  if unsafe { console_on_any(&others) } {
+    return false;
+  }
+
+  msys_tty_on(out)
+}
+
+pub fn is_a_color_terminal(out: &Term) -> bool {
+  if !is_a_terminal(out) {
+    return false;
+  }
+  if msys_tty_on(out) {
+    return match env::var("TERM") {
+      Ok(term) => term != "dumb",
+      Err(_) => true,
+    };
+  }
+  enable_ansi_on(out)
+}
+
+fn enable_ansi_on(out: &Term) -> bool {
+  unsafe {
+    let handle = as_handle(out);
+
+    let mut dw_mode = 0;
+    if GetConsoleMode(handle, &mut dw_mode) == 0 {
+      return false;
+    }
+
+    dw_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+    if SetConsoleMode(handle, dw_mode) == 0 {
+      return false;
+    }
+
+    true
+  }
+}
+
+unsafe fn console_on_any(fds: &[DWORD]) -> bool {
+  for &fd in fds {
+    let mut out = 0;
+    let handle = GetStdHandle(fd);
+    if GetConsoleMode(handle, &mut out) != 0 {
+      return true;
+    }
+  }
+  false
+}
+
+#[inline]
+pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {
+  terminal_size::terminal_size_using_handle(out.as_raw_handle()).map(|x| ((x.1).0, (x.0).0))
+}
+
+pub fn move_cursor_to(out: &Term, x: usize, y: usize) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::move_cursor_to(out, x, y);
+  }
+  if let Some((hand, _)) = get_console_screen_buffer_info(as_handle(out)) {
+    unsafe {
+      SetConsoleCursorPosition(
+        hand,
+        COORD {
+          X: x as i16,
+          Y: y as i16,
+        },
+      );
+    }
+  }
+  Ok(())
+}
+
+pub fn move_cursor_up(out: &Term, n: usize) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::move_cursor_up(out, n);
+  }
+
+  if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
+    move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize - n)?;
+  }
+  Ok(())
+}
+
+pub fn move_cursor_down(out: &Term, n: usize) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::move_cursor_down(out, n);
+  }
+
+  if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
+    move_cursor_to(out, 0, csbi.dwCursorPosition.Y as usize + n)?;
+  }
+  Ok(())
+}
+
+pub fn move_cursor_left(out: &Term, n: usize) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::move_cursor_left(out, n);
+  }
+
+  if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
+    move_cursor_to(
+      out,
+      csbi.dwCursorPosition.X as usize - n,
+      csbi.dwCursorPosition.Y as usize,
+    )?;
+  }
+  Ok(())
+}
+
+pub fn move_cursor_right(out: &Term, n: usize) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::move_cursor_right(out, n);
+  }
+
+  if let Some((_, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
+    move_cursor_to(
+      out,
+      csbi.dwCursorPosition.X as usize + n,
+      csbi.dwCursorPosition.Y as usize,
+    )?;
+  }
+  Ok(())
+}
+
+pub fn clear_line(out: &Term) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::clear_line(out);
+  }
+  if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
+    unsafe {
+      let width = csbi.srWindow.Right - csbi.srWindow.Left;
+      let pos = COORD {
+        X: 0,
+        Y: csbi.dwCursorPosition.Y,
+      };
+      let mut written = 0;
+      FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written);
+      FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written);
+      SetConsoleCursorPosition(hand, pos);
+    }
+  }
+  Ok(())
+}
+
+pub fn clear_chars(out: &Term, n: usize) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::clear_chars(out, n);
+  }
+  if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
+    unsafe {
+      let width = cmp::min(csbi.dwCursorPosition.X, n as i16);
+      let pos = COORD {
+        X: csbi.dwCursorPosition.X - width,
+        Y: csbi.dwCursorPosition.Y,
+      };
+      let mut written = 0;
+      FillConsoleOutputCharacterA(hand, b' ' as CHAR, width as DWORD, pos, &mut written);
+      FillConsoleOutputAttribute(hand, csbi.wAttributes, width as DWORD, pos, &mut written);
+      SetConsoleCursorPosition(hand, pos);
+    }
+  }
+  Ok(())
+}
+
+pub fn clear_screen(out: &Term) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::clear_screen(out);
+  }
+  if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
+    unsafe {
+      let cells = csbi.dwSize.X as DWORD * csbi.dwSize.Y as DWORD; // as DWORD, or else this causes stack overflows.
+      let pos = COORD { X: 0, Y: 0 };
+      let mut written = 0;
+      FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as DWORD no longer needed.
+      FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written);
+      SetConsoleCursorPosition(hand, pos);
+    }
+  }
+  Ok(())
+}
+
+pub fn clear_to_end_of_screen(out: &Term) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::clear_to_end_of_screen(out);
+  }
+  if let Some((hand, csbi)) = get_console_screen_buffer_info(as_handle(out)) {
+    unsafe {
+      let bottom = csbi.srWindow.Right as DWORD * csbi.srWindow.Bottom as DWORD;
+      let cells = bottom - (csbi.dwCursorPosition.X as DWORD * csbi.dwCursorPosition.Y as DWORD); // as DWORD, or else this causes stack overflows.
+      let pos = COORD {
+        X: 0,
+        Y: csbi.dwCursorPosition.Y,
+      };
+      let mut written = 0;
+      FillConsoleOutputCharacterA(hand, b' ' as CHAR, cells, pos, &mut written); // cells as DWORD no longer needed.
+      FillConsoleOutputAttribute(hand, csbi.wAttributes, cells, pos, &mut written);
+      SetConsoleCursorPosition(hand, pos);
+    }
+  }
+  Ok(())
+}
+
+pub fn show_cursor(out: &Term) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::show_cursor(out);
+  }
+  if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) {
+    unsafe {
+      cci.bVisible = 1;
+      SetConsoleCursorInfo(hand, &cci);
+    }
+  }
+  Ok(())
+}
+
+pub fn hide_cursor(out: &Term) -> io::Result<()> {
+  if out.is_msys_tty {
+    return common_term::hide_cursor(out);
+  }
+  if let Some((hand, mut cci)) = get_console_cursor_info(as_handle(out)) {
+    unsafe {
+      cci.bVisible = 0;
+      SetConsoleCursorInfo(hand, &cci);
+    }
+  }
+  Ok(())
+}
+
+fn get_console_screen_buffer_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_SCREEN_BUFFER_INFO)> {
+  let mut csbi: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() };
+  match unsafe { GetConsoleScreenBufferInfo(hand, &mut csbi) } {
+    0 => None,
+    _ => Some((hand, csbi)),
+  }
+}
+
+fn get_console_cursor_info(hand: HANDLE) -> Option<(HANDLE, CONSOLE_CURSOR_INFO)> {
+  let mut cci: CONSOLE_CURSOR_INFO = unsafe { mem::zeroed() };
+  match unsafe { GetConsoleCursorInfo(hand, &mut cci) } {
+    0 => None,
+    _ => Some((hand, cci)),
+  }
+}
+
+pub fn key_from_key_code(code: INT) -> Key {
+  match code {
+    winapi::um::winuser::VK_LEFT => Key::ArrowLeft,
+    winapi::um::winuser::VK_RIGHT => Key::ArrowRight,
+    winapi::um::winuser::VK_UP => Key::ArrowUp,
+    winapi::um::winuser::VK_DOWN => Key::ArrowDown,
+    winapi::um::winuser::VK_RETURN => Key::Enter,
+    winapi::um::winuser::VK_ESCAPE => Key::Escape,
+    winapi::um::winuser::VK_BACK => Key::Backspace,
+    winapi::um::winuser::VK_TAB => Key::Tab,
+    winapi::um::winuser::VK_HOME => Key::Home,
+    winapi::um::winuser::VK_END => Key::End,
+    winapi::um::winuser::VK_DELETE => Key::Del,
+    winapi::um::winuser::VK_SHIFT => Key::Shift,
+    _ => Key::Unknown,
+  }
+}
+
+pub fn read_secure() -> io::Result<String> {
+  let mut rv = String::new();
+  loop {
+    match read_single_key()? {
+      Key::Enter => {
+        break;
+      }
+      Key::Char('\x08') => {
+        if !rv.is_empty() {
+          let new_len = rv.len() - 1;
+          rv.truncate(new_len);
+        }
+      }
+      Key::Char(c) => {
+        rv.push(c);
+      }
+      _ => {}
+    }
+  }
+  Ok(rv)
+}
+
+pub fn read_single_key() -> io::Result<Key> {
+  let key_event = read_key_event()?;
+
+  let unicode_char = unsafe { *key_event.uChar.UnicodeChar() };
+  if unicode_char == 0 {
+    Ok(key_from_key_code(key_event.wVirtualKeyCode as INT))
+  } else {
+    // This is a unicode character, in utf-16. Try to decode it by itself.
+    match char::from_utf16_tuple((unicode_char, None)) {
+      Ok(c) => {
+        // Maintain backward compatibility. The previous implementation (_getwch()) would return
+        // a special keycode for `Enter`, while ReadConsoleInputW() prefers to use '\r'.
+        if c == '\r' {
+          Ok(Key::Enter)
+        } else if c == '\x08' {
+          Ok(Key::Backspace)
+        } else if c == '\x1B' {
+          Ok(Key::Escape)
+        } else {
+          Ok(Key::Char(c))
+        }
+      }
+      // This is part of a surrogate pair. Try to read the second half.
+      Err(InvalidUtf16Tuple::MissingSecond) => {
+        // Confirm that there is a next character to read.
+        if get_key_event_count()? == 0 {
+          let message = format!(
+            "Read invlid utf16 {}: {}",
+            unicode_char,
+            InvalidUtf16Tuple::MissingSecond
+          );
+          return Err(io::Error::new(io::ErrorKind::InvalidData, message));
+        }
+
+        // Read the next character.
+        let next_event = read_key_event()?;
+        let next_surrogate = unsafe { *next_event.uChar.UnicodeChar() };
+
+        // Attempt to decode it.
+        match char::from_utf16_tuple((unicode_char, Some(next_surrogate))) {
+          Ok(c) => Ok(Key::Char(c)),
+
+          // Return an InvalidData error. This is the recommended value for UTF-related I/O errors.
+          // (This error is given when reading a non-UTF8 file into a String, for example.)
+          Err(e) => {
+            let message = format!(
+              "Read invalid surrogate pair ({}, {}): {}",
+              unicode_char, next_surrogate, e
+            );
+            Err(io::Error::new(io::ErrorKind::InvalidData, message))
+          }
+        }
+      }
+
+      // Return an InvalidData error. This is the recommended value for UTF-related I/O errors.
+      // (This error is given when reading a non-UTF8 file into a String, for example.)
+      Err(e) => {
+        let message = format!("Read invalid utf16 {}: {}", unicode_char, e);
+        Err(io::Error::new(io::ErrorKind::InvalidData, message))
+      }
+    }
+  }
+}
+
+fn get_stdin_handle() -> io::Result<HANDLE> {
+  let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
+  if handle == INVALID_HANDLE_VALUE {
+    Err(io::Error::last_os_error())
+  } else {
+    Ok(handle)
+  }
+}
+
+/// Get the number of pending events in the ReadConsoleInput queue. Note that while
+/// these aren't necessarily key events, the only way that multiple events can be
+/// put into the queue simultaneously is if a unicode character spanning multiple u16's
+/// is read.
+///
+/// Therefore, this is accurate as long as at least one KEY_EVENT has already been read.
+fn get_key_event_count() -> io::Result<DWORD> {
+  let handle = get_stdin_handle()?;
+  let mut event_count: DWORD = unsafe { mem::zeroed() };
+
+  let success = unsafe { GetNumberOfConsoleInputEvents(handle, &mut event_count) };
+  if success == 0 {
+    Err(io::Error::last_os_error())
+  } else {
+    Ok(event_count)
+  }
+}
+
+fn read_key_event() -> io::Result<KEY_EVENT_RECORD> {
+  let handle = get_stdin_handle()?;
+  let mut buffer: INPUT_RECORD = unsafe { mem::zeroed() };
+
+  let mut events_read: DWORD = unsafe { mem::zeroed() };
+
+  let mut key_event: KEY_EVENT_RECORD;
+  loop {
+    let success = unsafe { ReadConsoleInputW(handle, &mut buffer, 1, &mut events_read) };
+    if success == 0 {
+      return Err(io::Error::last_os_error());
+    }
+    if events_read == 0 {
+      return Err(io::Error::new(
+        io::ErrorKind::Other,
+        "ReadConsoleInput returned no events, instead of waiting for an event",
+      ));
+    }
+
+    if events_read == 1 && buffer.EventType != KEY_EVENT {
+      // This isn't a key event; ignore it.
+      continue;
+    }
+
+    key_event = unsafe { mem::transmute(buffer.Event) };
+
+    if key_event.bKeyDown == 0 {
+      // This is a key being released; ignore it.
+      continue;
+    }
+
+    return Ok(key_event);
+  }
+}
+
+pub fn wants_emoji() -> bool {
+  // If WT_SESSION is set, we can assume we're running in the nne
+  // Windows Terminal.  The correct way to detect this is not available
+  // yet.  See https://github.com/microsoft/terminal/issues/1040
+  env::var("WT_SESSION").is_ok()
+}
+
+/// Returns true if there is an MSYS tty on the given handle.
+pub fn msys_tty_on(term: &Term) -> bool {
+  let handle = term.as_raw_handle();
+  unsafe {
+    // Check whether the Windows 10 native pty is enabled
+    {
+      let mut out = MaybeUninit::uninit();
+      let res = GetConsoleMode(handle as *mut _, out.as_mut_ptr());
+      if res != 0 // If res is true then out was initialized.
+                && (out.assume_init() & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+                    == ENABLE_VIRTUAL_TERMINAL_PROCESSING
+      {
+        return true;
+      }
+    }
+
+    let size = mem::size_of::<FILE_NAME_INFO>();
+    let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()];
+    let res = GetFileInformationByHandleEx(
+      handle as *mut _,
+      FileNameInfo,
+      &mut *name_info_bytes as *mut _ as *mut c_void,
+      name_info_bytes.len() as u32,
+    );
+    if res == 0 {
+      return false;
+    }
+    let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO);
+    let s = slice::from_raw_parts(
+      name_info.FileName.as_ptr(),
+      name_info.FileNameLength as usize / 2,
+    );
+    let name = String::from_utf16_lossy(s);
+    // This checks whether 'pty' exists in the file name, which indicates that
+    // a pseudo-terminal is attached. To mitigate against false positives
+    // (e.g., an actual file name that contains 'pty'), we also require that
+    // either the strings 'msys-' or 'cygwin-' are in the file name as well.)
+    let is_msys = name.contains("msys-") || name.contains("cygwin-");
+    let is_pty = name.contains("-pty");
+    is_msys && is_pty
+  }
+}
+
+pub fn set_title<T: Display>(title: T) {
+  let buffer: Vec<u16> = OsStr::new(&format!("{}", title))
+    .encode_wide()
+    .chain(once(0))
+    .collect();
+  unsafe {
+    SetConsoleTitleW(buffer.as_ptr());
+  }
+}
+
+#[cfg(feature = "windows-console-colors")]
+pub fn console_colors(out: &Term, mut con: Console, bytes: &[u8]) -> io::Result<()> {
+  use super::ansi::AnsiCodeIterator;
+  use std::str::from_utf8;
+
+  let s = from_utf8(bytes).expect("data to be printed is not an ansi string");
+  let mut iter = AnsiCodeIterator::new(s);
+
+  while !iter.rest_slice().is_empty() {
+    if let Some((part, is_esc)) = iter.next() {
+      if !is_esc {
+        out.write_through_common(part.as_bytes())?;
+      } else if part == "\x1b[0m" {
+        con.reset()?;
+      } else if let Some(cap) = INTENSE_COLOR_RE.captures(part) {
+        let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
+
+        match cap.get(1).unwrap().as_str() {
+          "3" => con.fg(Intense::Yes, color)?,
+          "4" => con.bg(Intense::Yes, color)?,
+          _ => unreachable!(),
+        };
+      } else if let Some(cap) = NORMAL_COLOR_RE.captures(part) {
+        let color = get_color_from_ansi(cap.get(2).unwrap().as_str());
+
+        match cap.get(1).unwrap().as_str() {
+          "3" => con.fg(Intense::No, color)?,
+          "4" => con.bg(Intense::No, color)?,
+          _ => unreachable!(),
+        };
+      } else if !ATTR_RE.is_match(part) {
+        out.write_through_common(part.as_bytes())?;
+      }
+    }
+  }
+
+  Ok(())
+}
+
+#[cfg(feature = "windows-console-colors")]
+fn get_color_from_ansi(ansi: &str) -> Color {
+  match ansi {
+    "0" | "8" => Color::Black,
+    "1" | "9" => Color::Red,
+    "2" | "10" => Color::Green,
+    "3" | "11" => Color::Yellow,
+    "4" | "12" => Color::Blue,
+    "5" | "13" => Color::Magenta,
+    "6" | "14" => Color::Cyan,
+    "7" | "15" => Color::White,
+    _ => unreachable!(),
+  }
+}

+ 131 - 0
tooling/cli.rs/src/dialoguer/edit.rs

@@ -0,0 +1,131 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::{
+  env,
+  ffi::{OsStr, OsString},
+  fs, io,
+  io::{Read, Write},
+  process,
+};
+
+/// Launches the default editor to edit a string.
+///
+/// ## Example
+///
+/// ```rust,no_run
+/// use dialoguer::Editor;
+///
+/// if let Some(rv) = Editor::new().edit("Enter a commit message").unwrap() {
+///     println!("Your message:");
+///     println!("{}", rv);
+/// } else {
+///     println!("Abort!");
+/// }
+/// ```
+pub struct Editor {
+  editor: OsString,
+  extension: String,
+  require_save: bool,
+  trim_newlines: bool,
+}
+
+fn get_default_editor() -> OsString {
+  if let Some(prog) = env::var_os("VISUAL") {
+    return prog;
+  }
+  if let Some(prog) = env::var_os("EDITOR") {
+    return prog;
+  }
+  if cfg!(windows) {
+    "notepad.exe".into()
+  } else {
+    "vi".into()
+  }
+}
+
+impl Default for Editor {
+  fn default() -> Editor {
+    Editor::new()
+  }
+}
+
+impl Editor {
+  /// Creates a new editor.
+  pub fn new() -> Editor {
+    Editor {
+      editor: get_default_editor(),
+      extension: ".txt".into(),
+      require_save: true,
+      trim_newlines: true,
+    }
+  }
+
+  /// Sets a specific editor executable.
+  pub fn executable<S: AsRef<OsStr>>(&mut self, val: S) -> &mut Editor {
+    self.editor = val.as_ref().into();
+    self
+  }
+
+  /// Sets a specific extension
+  pub fn extension(&mut self, val: &str) -> &mut Editor {
+    self.extension = val.into();
+    self
+  }
+
+  /// Enables or disables the save requirement.
+  pub fn require_save(&mut self, val: bool) -> &mut Editor {
+    self.require_save = val;
+    self
+  }
+
+  /// Enables or disables trailing newline stripping.
+  ///
+  /// This is on by default.
+  pub fn trim_newlines(&mut self, val: bool) -> &mut Editor {
+    self.trim_newlines = val;
+    self
+  }
+
+  /// Launches the editor to edit a string.
+  ///
+  /// Returns `None` if the file was not saved or otherwise the
+  /// entered text.
+  pub fn edit(&self, s: &str) -> io::Result<Option<String>> {
+    let mut f = tempfile::Builder::new()
+      .prefix("edit-")
+      .suffix(&self.extension)
+      .rand_bytes(12)
+      .tempfile()?;
+    f.write_all(s.as_bytes())?;
+    f.flush()?;
+    let ts = fs::metadata(f.path())?.modified()?;
+
+    let s: String = self.editor.clone().into_string().unwrap();
+    let mut iterator = s.split(' ');
+    let cmd = iterator.next().unwrap();
+    let args: Vec<&str> = iterator.collect();
+
+    let rv = process::Command::new(cmd)
+      .args(args)
+      .arg(f.path())
+      .spawn()?
+      .wait()?;
+
+    if rv.success() && self.require_save && ts >= fs::metadata(f.path())?.modified()? {
+      return Ok(None);
+    }
+
+    let mut new_f = fs::File::open(f.path())?;
+    let mut rv = String::new();
+    new_f.read_to_string(&mut rv)?;
+
+    if self.trim_newlines {
+      let len = rv.trim_end_matches(&['\n', '\r'][..]).len();
+      rv.truncate(len);
+    }
+
+    Ok(Some(rv))
+  }
+}

+ 34 - 0
tooling/cli.rs/src/dialoguer/mod.rs

@@ -0,0 +1,34 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+//! dialoguer is a library for Rust that helps you build useful small
+//! interactive user inputs for the command line.  It provides utilities
+//! to render various simple dialogs like confirmation prompts, text
+//! inputs and more.
+//!
+//! Best paired with other libraries in the family:
+//!
+//! * [indicatif](https://docs.rs/indicatif)
+//! * [console](https://docs.rs/console)
+//!
+//! # Crate Contents
+//!
+//! * Confirmation prompts
+//! * Input prompts (regular and password)
+//! * Input validation
+//! * Selections prompts (single and multi)
+//! * Other kind of prompts
+//! * Editor launching
+
+pub use edit::Editor;
+pub use prompts::{
+  confirm::Confirm, input::Input, multi_select::MultiSelect, password::Password, select::Select,
+  sort::Sort,
+};
+pub use validate::Validator;
+
+mod edit;
+mod prompts;
+pub mod theme;
+mod validate;

+ 259 - 0
tooling/cli.rs/src/dialoguer/prompts/confirm.rs

@@ -0,0 +1,259 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::io;
+
+use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
+
+use crate::console::{Key, Term};
+
+/// Renders a confirm prompt.
+///
+/// ## Example usage
+///
+/// ```rust,no_run
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// use dialoguer::Confirm;
+///
+/// if Confirm::new().with_prompt("Do you want to continue?").interact()? {
+///     println!("Looks like you want to continue");
+/// } else {
+///     println!("nevermind then :(");
+/// }
+/// # Ok(()) } fn main() { test().unwrap(); }
+/// ```
+pub struct Confirm<'a> {
+  prompt: String,
+  default: Option<bool>,
+  show_default: bool,
+  wait_for_newline: bool,
+  theme: &'a dyn Theme,
+}
+
+impl<'a> Default for Confirm<'a> {
+  fn default() -> Confirm<'a> {
+    Confirm::new()
+  }
+}
+
+impl<'a> Confirm<'a> {
+  /// Creates a confirm prompt.
+  pub fn new() -> Confirm<'static> {
+    Confirm::with_theme(&SimpleTheme)
+  }
+
+  /// Creates a confirm prompt with a specific theme.
+  ///
+  /// ## Examples
+  /// ```rust,no_run
+  /// use dialoguer::{
+  ///     Confirm,
+  ///     theme::ColorfulTheme
+  /// };
+  ///
+  /// # fn main() -> std::io::Result<()> {
+  /// let proceed = Confirm::with_theme(&ColorfulTheme::default())
+  ///     .with_prompt("Do you wish to continue?")
+  ///     .interact()?;
+  /// #    Ok(())
+  /// # }
+  /// ```
+  pub fn with_theme(theme: &'a dyn Theme) -> Confirm<'a> {
+    Confirm {
+      prompt: "".into(),
+      default: None,
+      show_default: true,
+      wait_for_newline: false,
+      theme,
+    }
+  }
+
+  /// Sets the confirm prompt.
+  pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Confirm<'a> {
+    self.prompt = prompt.into();
+    self
+  }
+
+  #[deprecated(note = "Use with_prompt() instead", since = "0.6.0")]
+  #[inline]
+  pub fn with_text(&mut self, text: &str) -> &mut Confirm<'a> {
+    self.with_prompt(text)
+  }
+
+  /// Sets when to react to user input.
+  ///
+  /// When `false` (default), we check on each user keystroke immediately as
+  /// it is typed. Valid inputs can be one of 'y', 'n', or a newline to accept
+  /// the default.
+  ///
+  /// When `true`, the user must type their choice and hit the Enter key before
+  /// proceeding. Valid inputs can be "yes", "no", "y", "n", or an empty string
+  /// to accept the default.
+  pub fn wait_for_newline(&mut self, wait: bool) -> &mut Confirm<'a> {
+    self.wait_for_newline = wait;
+    self
+  }
+
+  /// Sets a default.
+  ///
+  /// Out of the box the prompt does not have a default and will continue
+  /// to display until the user inputs something and hits enter. If a default is set the user
+  /// can instead accept the default with enter.
+  pub fn default(&mut self, val: bool) -> &mut Confirm<'a> {
+    self.default = Some(val);
+    self
+  }
+
+  /// Disables or enables the default value display.
+  ///
+  /// The default is to append the default value to the prompt to tell the user.
+  pub fn show_default(&mut self, val: bool) -> &mut Confirm<'a> {
+    self.show_default = val;
+    self
+  }
+
+  /// Enables user interaction and returns the result.
+  ///
+  /// If the user confirms the result is `true`, `false` if declines or default (configured in [default](#method.default)) if pushes enter.
+  /// Otherwise function discards input waiting for valid one.
+  ///
+  /// The dialog is rendered on stderr.
+  pub fn interact(&self) -> io::Result<bool> {
+    self.interact_on(&Term::stderr())
+  }
+
+  /// Enables user interaction and returns the result.
+  ///
+  /// This method is similar to [interact_on_opt](#method.interact_on_opt) except for the fact that it does not allow selection of the terminal.
+  /// The dialog is rendered on stderr.
+  /// Result contains `Some(bool)` if user answered "yes" or "no" or `None` if user cancelled with 'Esc' or 'q'.
+  pub fn interact_opt(&self) -> io::Result<Option<bool>> {
+    self.interact_on_opt(&Term::stderr())
+  }
+
+  /// Like [interact](#method.interact) but allows a specific terminal to be set.
+  ///
+  /// ## Examples
+  ///
+  /// ```rust,no_run
+  /// use dialoguer::Confirm;
+  /// use console::Term;
+  ///
+  /// # fn main() -> std::io::Result<()> {
+  /// let proceed = Confirm::new()
+  ///     .with_prompt("Do you wish to continue?")
+  ///     .interact_on(&Term::stderr())?;
+  /// #   Ok(())
+  /// # }
+  /// ```
+  pub fn interact_on(&self, term: &Term) -> io::Result<bool> {
+    self
+      ._interact_on(term, false)?
+      .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
+  }
+
+  /// Like [interact_opt](#method.interact_opt) but allows a specific terminal to be set.
+  ///
+  /// ## Examples
+  /// ```rust,no_run
+  /// use dialoguer::Confirm;
+  /// use console::Term;
+  ///
+  /// fn main() -> std::io::Result<()> {
+  ///     let confirmation = Confirm::new()
+  ///         .interact_on_opt(&Term::stdout())?;
+  ///
+  ///     match confirmation {
+  ///         Some(answer) => println!("User answered {}", if answer { "yes" } else { "no " }),
+  ///         None => println!("User did not answer")
+  ///     }
+  ///
+  ///     Ok(())
+  /// }
+  /// ```
+  pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<bool>> {
+    self._interact_on(term, true)
+  }
+
+  fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<bool>> {
+    let mut render = TermThemeRenderer::new(term, self.theme);
+
+    let default_if_show = if self.show_default {
+      self.default
+    } else {
+      None
+    };
+
+    render.confirm_prompt(&self.prompt, default_if_show)?;
+
+    term.hide_cursor()?;
+    term.flush()?;
+
+    let rv;
+
+    if self.wait_for_newline {
+      // Waits for user input and for the user to hit the Enter key
+      // before validation.
+      let mut value = default_if_show;
+
+      loop {
+        let input = term.read_key()?;
+
+        match input {
+          Key::Char('y') | Key::Char('Y') => {
+            value = Some(true);
+          }
+          Key::Char('n') | Key::Char('N') => {
+            value = Some(false);
+          }
+          Key::Enter => {
+            if !allow_quit {
+              value = value.or(self.default);
+            }
+
+            if value.is_some() || allow_quit {
+              rv = value;
+              break;
+            }
+            continue;
+          }
+          Key::Escape | Key::Char('q') if allow_quit => {
+            value = None;
+          }
+          _ => {
+            continue;
+          }
+        };
+
+        term.clear_line()?;
+        render.confirm_prompt(&self.prompt, value)?;
+      }
+    } else {
+      // Default behavior: matches continuously on every keystroke,
+      // and does not wait for user to hit the Enter key.
+      loop {
+        let input = term.read_key()?;
+        let value = match input {
+          Key::Char('y') | Key::Char('Y') => Some(true),
+          Key::Char('n') | Key::Char('N') => Some(false),
+          Key::Enter if self.default.is_some() => Some(self.default.unwrap()),
+          Key::Escape | Key::Char('q') if allow_quit => None,
+          _ => {
+            continue;
+          }
+        };
+
+        rv = value;
+        break;
+      }
+    }
+
+    term.clear_line()?;
+    render.confirm_prompt_selection(&self.prompt, rv)?;
+    term.show_cursor()?;
+    term.flush()?;
+
+    Ok(rv)
+  }
+}

+ 361 - 0
tooling/cli.rs/src/dialoguer/prompts/input.rs

@@ -0,0 +1,361 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::{
+  fmt::{Debug, Display},
+  io, iter,
+  str::FromStr,
+};
+
+use super::super::{
+  theme::{SimpleTheme, TermThemeRenderer, Theme},
+  validate::Validator,
+};
+
+use crate::console::{Key, Term};
+
+/// Renders an input prompt.
+///
+/// ## Example usage
+///
+/// ```rust,no_run
+/// use dialoguer::Input;
+///
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// let input : String = Input::new()
+///     .with_prompt("Tea or coffee?")
+///     .with_initial_text("Yes")
+///     .default("No".into())
+///     .interact_text()?;
+/// # Ok(())
+/// # }
+/// ```
+/// It can also be used with turbofish notation:
+///
+/// ```rust,no_run
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// # use dialoguer::Input;
+/// let input = Input::<String>::new()
+///     .interact_text()?;
+/// # Ok(())
+/// # }
+/// ```
+pub struct Input<'a, T> {
+  prompt: String,
+  default: Option<T>,
+  show_default: bool,
+  initial_text: Option<String>,
+  theme: &'a dyn Theme,
+  permit_empty: bool,
+  validator: Option<Box<dyn FnMut(&T) -> Option<String> + 'a>>,
+}
+
+impl<'a, T> Default for Input<'a, T>
+where
+  T: Clone + FromStr + Display,
+  T::Err: Display + Debug,
+{
+  fn default() -> Input<'a, T> {
+    Input::new()
+  }
+}
+
+impl<'a, T> Input<'a, T>
+where
+  T: Clone + FromStr + Display,
+  T::Err: Display + Debug,
+{
+  /// Creates an input prompt.
+  pub fn new() -> Input<'a, T> {
+    Input::with_theme(&SimpleTheme)
+  }
+
+  /// Creates an input prompt with a specific theme.
+  pub fn with_theme(theme: &'a dyn Theme) -> Input<'a, T> {
+    Input {
+      prompt: "".into(),
+      default: None,
+      show_default: true,
+      initial_text: None,
+      theme,
+      permit_empty: false,
+      validator: None,
+    }
+  }
+
+  /// Sets the input prompt.
+  pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Input<'a, T> {
+    self.prompt = prompt.into();
+    self
+  }
+
+  /// Sets initial text that user can accept or erase.
+  pub fn with_initial_text<S: Into<String>>(&mut self, val: S) -> &mut Input<'a, T> {
+    self.initial_text = Some(val.into());
+    self
+  }
+
+  /// Sets a default.
+  ///
+  /// Out of the box the prompt does not have a default and will continue
+  /// to display until the user inputs something and hits enter. If a default is set the user
+  /// can instead accept the default with enter.
+  pub fn default(&mut self, value: T) -> &mut Input<'a, T> {
+    self.default = Some(value);
+    self
+  }
+
+  /// Enables or disables an empty input
+  ///
+  /// By default, if there is no default value set for the input, the user must input a non-empty string.
+  pub fn allow_empty(&mut self, val: bool) -> &mut Input<'a, T> {
+    self.permit_empty = val;
+    self
+  }
+
+  /// Disables or enables the default value display.
+  ///
+  /// The default behaviour is to append [`default`] to the prompt to tell the
+  /// user what is the default value.
+  ///
+  /// This method does not affect existance of default value, only its display in the prompt!
+  pub fn show_default(&mut self, val: bool) -> &mut Input<'a, T> {
+    self.show_default = val;
+    self
+  }
+
+  /// Registers a validator.
+  ///
+  /// # Example
+  ///
+  /// ```no_run
+  /// # use dialoguer::Input;
+  /// let mail: String = Input::new()
+  ///     .with_prompt("Enter email")
+  ///     .validate_with(|input: &String| -> Result<(), &str> {
+  ///         if input.contains('@') {
+  ///             Ok(())
+  ///         } else {
+  ///             Err("This is not a mail address")
+  ///         }
+  ///     })
+  ///     .interact()
+  ///     .unwrap();
+  /// ```
+  pub fn validate_with<V>(&mut self, mut validator: V) -> &mut Input<'a, T>
+  where
+    V: Validator<T> + 'a,
+    T: 'a,
+  {
+    let mut old_validator_func = self.validator.take();
+
+    self.validator = Some(Box::new(move |value: &T| -> Option<String> {
+      if let Some(old) = old_validator_func.as_mut() {
+        if let Some(err) = old(value) {
+          return Some(err);
+        }
+      }
+
+      match validator.validate(value) {
+        Ok(()) => None,
+        Err(err) => Some(err.to_string()),
+      }
+    }));
+
+    self
+  }
+
+  /// Enables the user to enter a printable ascii sequence and returns the result.
+  ///
+  /// Its difference from [`interact`](#method.interact) is that it only allows ascii characters for string,
+  /// while [`interact`](#method.interact) allows virtually any character to be used e.g arrow keys.
+  ///
+  /// The dialog is rendered on stderr.
+  pub fn interact_text(&mut self) -> io::Result<T> {
+    self.interact_text_on(&Term::stderr())
+  }
+
+  /// Like [`interact_text`](#method.interact_text) but allows a specific terminal to be set.
+  pub fn interact_text_on(&mut self, term: &Term) -> io::Result<T> {
+    let mut render = TermThemeRenderer::new(term, self.theme);
+
+    loop {
+      let default_string = self.default.as_ref().map(|x| x.to_string());
+
+      render.input_prompt(
+        &self.prompt,
+        if self.show_default {
+          default_string.as_deref()
+        } else {
+          None
+        },
+      )?;
+      term.flush()?;
+
+      // Read input by keystroke so that we can suppress ascii control characters
+      if !term.features().is_attended() {
+        return Ok("".to_owned().parse::<T>().unwrap());
+      }
+
+      let mut chars: Vec<char> = Vec::new();
+      let mut position = 0;
+
+      if let Some(initial) = self.initial_text.as_ref() {
+        term.write_str(initial)?;
+        chars = initial.chars().collect();
+        position = chars.len();
+      }
+
+      loop {
+        match term.read_key()? {
+          Key::Backspace if position > 0 => {
+            position -= 1;
+            chars.remove(position);
+            term.clear_chars(1)?;
+
+            let tail: String = chars[position..].iter().collect();
+
+            if !tail.is_empty() {
+              term.write_str(&tail)?;
+              term.move_cursor_left(tail.len())?;
+            }
+
+            term.flush()?;
+          }
+          Key::Char(chr) if !chr.is_ascii_control() => {
+            chars.insert(position, chr);
+            position += 1;
+            let tail: String = iter::once(&chr).chain(chars[position..].iter()).collect();
+            term.write_str(&tail)?;
+            term.move_cursor_left(tail.len() - 1)?;
+            term.flush()?;
+          }
+          Key::ArrowLeft if position > 0 => {
+            term.move_cursor_left(1)?;
+            position -= 1;
+            term.flush()?;
+          }
+          Key::ArrowRight if position < chars.len() => {
+            term.move_cursor_right(1)?;
+            position += 1;
+            term.flush()?;
+          }
+          Key::Enter => break,
+          Key::Unknown => {
+            return Err(io::Error::new(
+              io::ErrorKind::NotConnected,
+              "Not a terminal",
+            ))
+          }
+          _ => (),
+        }
+      }
+      let input = chars.iter().collect::<String>();
+
+      term.clear_line()?;
+      render.clear()?;
+
+      if chars.is_empty() {
+        if let Some(ref default) = self.default {
+          render.input_prompt_selection(&self.prompt, &default.to_string())?;
+          term.flush()?;
+          return Ok(default.clone());
+        } else if !self.permit_empty {
+          continue;
+        }
+      }
+
+      match input.parse::<T>() {
+        Ok(value) => {
+          if let Some(ref mut validator) = self.validator {
+            if let Some(err) = validator(&value) {
+              render.error(&err)?;
+              continue;
+            }
+          }
+
+          render.input_prompt_selection(&self.prompt, &input)?;
+          term.flush()?;
+
+          return Ok(value);
+        }
+        Err(err) => {
+          render.error(&err.to_string())?;
+          continue;
+        }
+      }
+    }
+  }
+
+  /// Enables user interaction and returns the result.
+  ///
+  /// Allows any characters as input, including e.g arrow keys.
+  /// Some of the keys might have undesired behavior.
+  /// For more limited version, see [`interact_text`](#method.interact_text).
+  ///
+  /// If the user confirms the result is `true`, `false` otherwise.
+  /// The dialog is rendered on stderr.
+  pub fn interact(&mut self) -> io::Result<T> {
+    self.interact_on(&Term::stderr())
+  }
+
+  /// Like [`interact`](#method.interact) but allows a specific terminal to be set.
+  pub fn interact_on(&mut self, term: &Term) -> io::Result<T> {
+    let mut render = TermThemeRenderer::new(term, self.theme);
+
+    loop {
+      let default_string = self.default.as_ref().map(|x| x.to_string());
+
+      render.input_prompt(
+        &self.prompt,
+        if self.show_default {
+          default_string.as_deref()
+        } else {
+          None
+        },
+      )?;
+      term.flush()?;
+
+      let input = if let Some(initial_text) = self.initial_text.as_ref() {
+        term.read_line_initial_text(initial_text)?
+      } else {
+        term.read_line()?
+      };
+
+      render.add_line();
+      term.clear_line()?;
+      render.clear()?;
+
+      if input.is_empty() {
+        if let Some(ref default) = self.default {
+          render.input_prompt_selection(&self.prompt, &default.to_string())?;
+          term.flush()?;
+          return Ok(default.clone());
+        } else if !self.permit_empty {
+          continue;
+        }
+      }
+
+      match input.parse::<T>() {
+        Ok(value) => {
+          if let Some(ref mut validator) = self.validator {
+            if let Some(err) = validator(&value) {
+              render.error(&err)?;
+              continue;
+            }
+          }
+
+          render.input_prompt_selection(&self.prompt, &input)?;
+          term.flush()?;
+
+          return Ok(value);
+        }
+        Err(err) => {
+          render.error(&err.to_string())?;
+          continue;
+        }
+      }
+    }
+  }
+}

+ 12 - 0
tooling/cli.rs/src/dialoguer/prompts/mod.rs

@@ -0,0 +1,12 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#![allow(clippy::needless_doctest_main)]
+
+pub mod confirm;
+pub mod input;
+pub mod multi_select;
+pub mod password;
+pub mod select;
+pub mod sort;

+ 290 - 0
tooling/cli.rs/src/dialoguer/prompts/multi_select.rs

@@ -0,0 +1,290 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::{io, iter::repeat, ops::Rem};
+
+use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
+
+use crate::console::{Key, Term};
+
+/// Renders a multi select prompt.
+///
+/// ## Example usage
+/// ```rust,no_run
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// use dialoguer::MultiSelect;
+///
+/// let items = vec!["Option 1", "Option 2"];
+/// let chosen : Vec<usize> = MultiSelect::new()
+///     .items(&items)
+///     .interact()?;
+/// # Ok(())
+/// # }
+/// ```
+pub struct MultiSelect<'a> {
+  defaults: Vec<bool>,
+  items: Vec<String>,
+  prompt: Option<String>,
+  clear: bool,
+  theme: &'a dyn Theme,
+  paged: bool,
+}
+
+impl<'a> Default for MultiSelect<'a> {
+  fn default() -> MultiSelect<'a> {
+    MultiSelect::new()
+  }
+}
+
+impl<'a> MultiSelect<'a> {
+  /// Creates a multi select prompt.
+  pub fn new() -> MultiSelect<'static> {
+    MultiSelect::with_theme(&SimpleTheme)
+  }
+
+  /// Creates a multi select prompt with a specific theme.
+  pub fn with_theme(theme: &'a dyn Theme) -> MultiSelect<'a> {
+    MultiSelect {
+      items: vec![],
+      defaults: vec![],
+      clear: true,
+      prompt: None,
+      theme,
+      paged: false,
+    }
+  }
+
+  /// Enables or disables paging
+  pub fn paged(&mut self, val: bool) -> &mut MultiSelect<'a> {
+    self.paged = val;
+    self
+  }
+
+  /// Sets the clear behavior of the menu.
+  ///
+  /// The default is to clear the menu.
+  pub fn clear(&mut self, val: bool) -> &mut MultiSelect<'a> {
+    self.clear = val;
+    self
+  }
+
+  /// Sets a defaults for the menu.
+  pub fn defaults(&mut self, val: &[bool]) -> &mut MultiSelect<'a> {
+    self.defaults = val
+      .to_vec()
+      .iter()
+      .cloned()
+      .chain(repeat(false))
+      .take(self.items.len())
+      .collect();
+    self
+  }
+
+  /// Add a single item to the selector.
+  #[inline]
+  pub fn item<T: ToString>(&mut self, item: T) -> &mut MultiSelect<'a> {
+    self.item_checked(item, false)
+  }
+
+  /// Add a single item to the selector with a default checked state.
+  pub fn item_checked<T: ToString>(&mut self, item: T, checked: bool) -> &mut MultiSelect<'a> {
+    self.items.push(item.to_string());
+    self.defaults.push(checked);
+    self
+  }
+
+  /// Adds multiple items to the selector.
+  pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut MultiSelect<'a> {
+    for item in items {
+      self.items.push(item.to_string());
+      self.defaults.push(false);
+    }
+    self
+  }
+
+  /// Adds multiple items to the selector with checked state
+  pub fn items_checked<T: ToString>(&mut self, items: &[(T, bool)]) -> &mut MultiSelect<'a> {
+    for &(ref item, checked) in items {
+      self.items.push(item.to_string());
+      self.defaults.push(checked);
+    }
+    self
+  }
+
+  /// Prefaces the menu with a prompt.
+  ///
+  /// When a prompt is set the system also prints out a confirmation after
+  /// the selection.
+  pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut MultiSelect<'a> {
+    self.prompt = Some(prompt.into());
+    self
+  }
+
+  /// Enables user interaction and returns the result.
+  ///
+  /// The user can select the items with the space bar and on enter
+  /// the selected items will be returned.
+  pub fn interact(&self) -> io::Result<Vec<usize>> {
+    self.interact_on(&Term::stderr())
+  }
+
+  /// Like [interact](#method.interact) but allows a specific terminal to be set.
+  pub fn interact_on(&self, term: &Term) -> io::Result<Vec<usize>> {
+    let mut page = 0;
+
+    if self.items.is_empty() {
+      return Err(io::Error::new(
+        io::ErrorKind::Other,
+        "Empty list of items given to `MultiSelect`",
+      ));
+    }
+
+    let capacity = if self.paged {
+      term.size().0 as usize - 1
+    } else {
+      self.items.len()
+    };
+
+    let pages = (self.items.len() as f64 / capacity as f64).ceil() as usize;
+
+    let mut render = TermThemeRenderer::new(term, self.theme);
+    let mut sel = 0;
+
+    if let Some(ref prompt) = self.prompt {
+      render.multi_select_prompt(prompt)?;
+    }
+
+    let mut size_vec = Vec::new();
+
+    for items in self
+      .items
+      .iter()
+      .flat_map(|i| i.split('\n'))
+      .collect::<Vec<_>>()
+    {
+      let size = &items.len();
+      size_vec.push(*size);
+    }
+
+    let mut checked: Vec<bool> = self.defaults.clone();
+
+    loop {
+      for (idx, item) in self
+        .items
+        .iter()
+        .enumerate()
+        .skip(page * capacity)
+        .take(capacity)
+      {
+        render.multi_select_prompt_item(item, checked[idx], sel == idx)?;
+      }
+
+      term.hide_cursor()?;
+      term.flush()?;
+
+      match term.read_key()? {
+        Key::ArrowDown | Key::Char('j') => {
+          if sel == !0 {
+            sel = 0;
+          } else {
+            sel = (sel as u64 + 1).rem(self.items.len() as u64) as usize;
+          }
+        }
+        Key::ArrowUp | Key::Char('k') => {
+          if sel == !0 {
+            sel = self.items.len() - 1;
+          } else {
+            sel = ((sel as i64 - 1 + self.items.len() as i64) % (self.items.len() as i64)) as usize;
+          }
+        }
+        Key::ArrowLeft | Key::Char('h') => {
+          if self.paged {
+            if page == 0 {
+              page = pages - 1;
+            } else {
+              page -= 1;
+            }
+
+            sel = page * capacity;
+          }
+        }
+        Key::ArrowRight | Key::Char('l') => {
+          if self.paged {
+            if page == pages - 1 {
+              page = 0;
+            } else {
+              page += 1;
+            }
+
+            sel = page * capacity;
+          }
+        }
+        Key::Char(' ') => {
+          checked[sel] = !checked[sel];
+        }
+        Key::Escape => {
+          if self.clear {
+            render.clear()?;
+          }
+
+          if let Some(ref prompt) = self.prompt {
+            render.multi_select_prompt_selection(prompt, &[][..])?;
+          }
+
+          term.show_cursor()?;
+          term.flush()?;
+
+          return Ok(
+            self
+              .defaults
+              .clone()
+              .into_iter()
+              .enumerate()
+              .filter_map(|(idx, checked)| if checked { Some(idx) } else { None })
+              .collect(),
+          );
+        }
+        Key::Enter => {
+          if self.clear {
+            render.clear()?;
+          }
+
+          if let Some(ref prompt) = self.prompt {
+            let selections: Vec<_> = checked
+              .iter()
+              .enumerate()
+              .filter_map(|(idx, &checked)| {
+                if checked {
+                  Some(self.items[idx].as_str())
+                } else {
+                  None
+                }
+              })
+              .collect();
+
+            render.multi_select_prompt_selection(prompt, &selections[..])?;
+          }
+
+          term.show_cursor()?;
+          term.flush()?;
+
+          return Ok(
+            checked
+              .into_iter()
+              .enumerate()
+              .filter_map(|(idx, checked)| if checked { Some(idx) } else { None })
+              .collect(),
+          );
+        }
+        _ => {}
+      }
+
+      if sel < page * capacity || sel >= (page + 1) * capacity {
+        page = sel / capacity;
+      }
+
+      render.clear_preserve_prompt(&size_vec)?;
+    }
+  }
+}

+ 130 - 0
tooling/cli.rs/src/dialoguer/prompts/password.rs

@@ -0,0 +1,130 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::io;
+
+use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
+
+use crate::console::Term;
+use zeroize::Zeroizing;
+
+/// Renders a password input prompt.
+///
+/// ## Example usage
+///
+/// ```rust,no_run
+/// # fn test() -> Result<(), Box<std::error::Error>> {
+/// use dialoguer::Password;
+///
+/// let password = Password::new().with_prompt("New Password")
+///     .with_confirmation("Confirm password", "Passwords mismatching")
+///     .interact()?;
+/// println!("Length of the password is: {}", password.len());
+/// # Ok(()) } fn main() { test().unwrap(); }
+/// ```
+pub struct Password<'a> {
+  prompt: String,
+  theme: &'a dyn Theme,
+  allow_empty_password: bool,
+  confirmation_prompt: Option<(String, String)>,
+}
+
+impl<'a> Default for Password<'a> {
+  fn default() -> Password<'a> {
+    Password::new()
+  }
+}
+
+impl<'a> Password<'a> {
+  /// Creates a password input prompt.
+  pub fn new() -> Password<'static> {
+    Password::with_theme(&SimpleTheme)
+  }
+
+  /// Creates a password input prompt with a specific theme.
+  pub fn with_theme(theme: &'a dyn Theme) -> Password<'a> {
+    Password {
+      prompt: "".into(),
+      theme,
+      allow_empty_password: false,
+      confirmation_prompt: None,
+    }
+  }
+
+  /// Sets the password input prompt.
+  pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Password<'a> {
+    self.prompt = prompt.into();
+    self
+  }
+
+  /// Enables confirmation prompting.
+  pub fn with_confirmation<A, B>(&mut self, prompt: A, mismatch_err: B) -> &mut Password<'a>
+  where
+    A: Into<String>,
+    B: Into<String>,
+  {
+    self.confirmation_prompt = Some((prompt.into(), mismatch_err.into()));
+    self
+  }
+
+  /// Allows/Disables empty password.
+  ///
+  /// By default this setting is set to false (i.e. password is not empty).
+  pub fn allow_empty_password(&mut self, allow_empty_password: bool) -> &mut Password<'a> {
+    self.allow_empty_password = allow_empty_password;
+    self
+  }
+
+  /// Enables user interaction and returns the result.
+  ///
+  /// If the user confirms the result is `true`, `false` otherwise.
+  /// The dialog is rendered on stderr.
+  pub fn interact(&self) -> io::Result<String> {
+    self.interact_on(&Term::stderr())
+  }
+
+  /// Like `interact` but allows a specific terminal to be set.
+  pub fn interact_on(&self, term: &Term) -> io::Result<String> {
+    let mut render = TermThemeRenderer::new(term, self.theme);
+    render.set_prompts_reset_height(false);
+
+    loop {
+      let password = Zeroizing::new(self.prompt_password(&mut render, &self.prompt)?);
+
+      if let Some((ref prompt, ref err)) = self.confirmation_prompt {
+        let pw2 = Zeroizing::new(self.prompt_password(&mut render, &prompt)?);
+
+        if *password == *pw2 {
+          render.clear()?;
+          render.password_prompt_selection(&self.prompt)?;
+          term.flush()?;
+          return Ok((*password).clone());
+        }
+
+        render.error(err)?;
+      } else {
+        render.clear()?;
+        render.password_prompt_selection(&self.prompt)?;
+        term.flush()?;
+
+        return Ok((*password).clone());
+      }
+    }
+  }
+
+  fn prompt_password(&self, render: &mut TermThemeRenderer, prompt: &str) -> io::Result<String> {
+    loop {
+      render.password_prompt(prompt)?;
+      render.term().flush()?;
+
+      let input = render.term().read_secure_line()?;
+
+      render.add_line();
+
+      if !input.is_empty() || self.allow_empty_password {
+        return Ok(input);
+      }
+    }
+  }
+}

+ 418 - 0
tooling/cli.rs/src/dialoguer/prompts/select.rs

@@ -0,0 +1,418 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::{io, ops::Rem};
+
+use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
+
+use crate::console::{Key, Term};
+
+/// Renders a select prompt.
+///
+/// User can select from one or more options.
+/// Interaction returns index of an item selected in the order they appear in `item` invocation or `items` slice.
+///
+/// ## Examples
+///
+/// ```rust,no_run
+/// use dialoguer::{
+///     Select,
+///     theme::ColorfulTheme
+/// };
+/// use console::Term;
+///
+/// fn main() -> std::io::Result<()> {
+///     let items = vec!["Item 1", "item 2"];
+///     let selection = Select::with_theme(&ColorfulTheme::default())
+///         .items(&items)
+///         .default(0)
+///         .interact_on_opt(&Term::stderr())?;
+///
+///     match selection {
+///         Some(index) => println!("User selected item : {}", items[index]),
+///         None => println!("User did not select anything")
+///     }
+///
+///     Ok(())
+/// }
+/// ```
+pub struct Select<'a> {
+  default: usize,
+  items: Vec<String>,
+  prompt: Option<String>,
+  clear: bool,
+  theme: &'a dyn Theme,
+  paged: bool,
+}
+
+impl<'a> Default for Select<'a> {
+  fn default() -> Select<'a> {
+    Select::new()
+  }
+}
+
+impl<'a> Select<'a> {
+  /// Creates a select prompt builder with default theme.
+  pub fn new() -> Select<'static> {
+    Select::with_theme(&SimpleTheme)
+  }
+
+  /// Creates a select prompt builder with a specific theme.
+  ///
+  /// ## Examples
+  /// ```rust,no_run
+  /// use dialoguer::{
+  ///     Select,
+  ///     theme::ColorfulTheme
+  /// };
+  ///
+  /// fn main() -> std::io::Result<()> {
+  ///     let selection = Select::with_theme(&ColorfulTheme::default())
+  ///         .item("Option A")
+  ///         .item("Option B")
+  ///         .interact()?;
+  ///
+  ///     Ok(())
+  /// }
+  /// ```
+  pub fn with_theme(theme: &'a dyn Theme) -> Select<'a> {
+    Select {
+      default: !0,
+      items: vec![],
+      prompt: None,
+      clear: true,
+      theme,
+      paged: false,
+    }
+  }
+
+  /// Enables or disables paging
+  ///
+  /// Paging is disabled by default
+  pub fn paged(&mut self, val: bool) -> &mut Select<'a> {
+    self.paged = val;
+    self
+  }
+
+  /// Indicates whether select menu should be erased from the screen after interaction.
+  ///
+  /// The default is to clear the menu.
+  pub fn clear(&mut self, val: bool) -> &mut Select<'a> {
+    self.clear = val;
+    self
+  }
+
+  /// Sets initial selected element when select menu is rendered
+  ///
+  /// Element is indicated by the index at which it appears in `item` method invocation or `items` slice.
+  pub fn default(&mut self, val: usize) -> &mut Select<'a> {
+    self.default = val;
+    self
+  }
+
+  /// Add a single item to the selector.
+  ///
+  /// ## Examples
+  /// ```rust,no_run
+  /// use dialoguer::Select;
+  ///
+  /// fn main() -> std::io::Result<()> {
+  ///     let selection: usize = Select::new()
+  ///         .item("Item 1")
+  ///         .item("Item 2")
+  ///         .interact()?;
+  ///
+  ///     Ok(())
+  /// }
+  /// ```
+  pub fn item<T: ToString>(&mut self, item: T) -> &mut Select<'a> {
+    self.items.push(item.to_string());
+    self
+  }
+
+  /// Adds multiple items to the selector.
+  ///
+  /// ## Examples
+  /// ```rust,no_run
+  /// use dialoguer::Select;
+  ///
+  /// fn main() -> std::io::Result<()> {
+  ///     let items = vec!["Item 1", "Item 2"];
+  ///     let selection: usize = Select::new()
+  ///         .items(&items)
+  ///         .interact()?;
+  ///
+  ///     println!("{}", items[selection]);
+  ///
+  ///     Ok(())
+  /// }
+  /// ```
+  pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Select<'a> {
+    for item in items {
+      self.items.push(item.to_string());
+    }
+    self
+  }
+
+  /// Sets the select prompt.
+  ///
+  /// When a prompt is set the system also prints out a confirmation after
+  /// the selection.
+  ///
+  /// ## Examples
+  /// ```rust,no_run
+  /// use dialoguer::Select;
+  ///
+  /// fn main() -> std::io::Result<()> {
+  ///     let selection = Select::new()
+  ///         .with_prompt("Which option do you prefer?")
+  ///         .item("Option A")
+  ///         .item("Option B")
+  ///         .interact()?;
+  ///
+  ///     Ok(())
+  /// }
+  /// ```
+  pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Select<'a> {
+    self.prompt = Some(prompt.into());
+    self
+  }
+
+  /// Enables user interaction and returns the result.
+  ///
+  /// Similar to [interact_on](#method.interact_on) except for the fact that it does not allow selection of the terminal.
+  /// The dialog is rendered on stderr.
+  /// Result contains index of a selected item.
+  pub fn interact(&self) -> io::Result<usize> {
+    self.interact_on(&Term::stderr())
+  }
+
+  /// Enables user interaction and returns the result.
+  ///
+  /// This method is similar to [interact_on_opt](#method.interact_on_opt) except for the fact that it does not allow selection of the terminal.
+  /// The dialog is rendered on stderr.
+  /// Result contains `Some(index)` if user selected one of items or `None` if user cancelled with 'Esc' or 'q'.
+  pub fn interact_opt(&self) -> io::Result<Option<usize>> {
+    self.interact_on_opt(&Term::stderr())
+  }
+
+  /// Like [interact](#method.interact) but allows a specific terminal to be set.
+  ///
+  /// ## Examples
+  ///```rust,no_run
+  /// use dialoguer::Select;
+  /// use console::Term;
+  ///
+  /// fn main() -> std::io::Result<()> {
+  ///     let selection = Select::new()
+  ///         .item("Option A")
+  ///         .item("Option B")
+  ///         .interact_on(&Term::stderr())?;
+  ///
+  ///     println!("User selected option at index {}", selection);
+  ///
+  ///     Ok(())
+  /// }
+  ///```
+  pub fn interact_on(&self, term: &Term) -> io::Result<usize> {
+    self
+      ._interact_on(term, false)?
+      .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Quit not allowed in this case"))
+  }
+
+  /// Like [interact_opt](#method.interact_opt) but allows a specific terminal to be set.
+  ///
+  /// ## Examples
+  /// ```rust,no_run
+  /// use dialoguer::Select;
+  /// use console::Term;
+  ///
+  /// fn main() -> std::io::Result<()> {
+  ///     let selection = Select::new()
+  ///         .item("Option A")
+  ///         .item("Option B")
+  ///         .interact_on_opt(&Term::stdout())?;
+  ///
+  ///     match selection {
+  ///         Some(position) => println!("User selected option at index {}", position),
+  ///         None => println!("User did not select anything")
+  ///     }
+  ///
+  ///     Ok(())
+  /// }
+  /// ```
+  #[inline]
+  pub fn interact_on_opt(&self, term: &Term) -> io::Result<Option<usize>> {
+    self._interact_on(term, true)
+  }
+
+  /// Like `interact` but allows a specific terminal to be set.
+  fn _interact_on(&self, term: &Term, allow_quit: bool) -> io::Result<Option<usize>> {
+    let mut page = 0;
+
+    if self.items.is_empty() {
+      return Err(io::Error::new(
+        io::ErrorKind::Other,
+        "Empty list of items given to `Select`",
+      ));
+    }
+
+    let capacity = if self.paged {
+      term.size().0 as usize - 1
+    } else {
+      self.items.len()
+    };
+
+    let pages = (self.items.len() as f64 / capacity as f64).ceil() as usize;
+
+    let mut render = TermThemeRenderer::new(term, self.theme);
+    let mut sel = self.default;
+
+    if let Some(ref prompt) = self.prompt {
+      render.select_prompt(prompt)?;
+    }
+
+    let mut size_vec = Vec::new();
+
+    for items in self
+      .items
+      .iter()
+      .flat_map(|i| i.split('\n'))
+      .collect::<Vec<_>>()
+    {
+      let size = &items.len();
+      size_vec.push(*size);
+    }
+
+    loop {
+      for (idx, item) in self
+        .items
+        .iter()
+        .enumerate()
+        .skip(page * capacity)
+        .take(capacity)
+      {
+        render.select_prompt_item(item, sel == idx)?;
+      }
+
+      term.hide_cursor()?;
+      term.flush()?;
+
+      match term.read_key()? {
+        Key::ArrowDown | Key::Char('j') => {
+          if sel == !0 {
+            sel = 0;
+          } else {
+            sel = (sel as u64 + 1).rem(self.items.len() as u64) as usize;
+          }
+        }
+        Key::Escape | Key::Char('q') => {
+          if allow_quit {
+            if self.clear {
+              term.clear_last_lines(self.items.len())?;
+              term.show_cursor()?;
+              term.flush()?;
+            }
+
+            return Ok(None);
+          }
+        }
+        Key::ArrowUp | Key::Char('k') => {
+          if sel == !0 {
+            sel = self.items.len() - 1;
+          } else {
+            sel = ((sel as i64 - 1 + self.items.len() as i64) % (self.items.len() as i64)) as usize;
+          }
+        }
+        Key::ArrowLeft | Key::Char('h') => {
+          if self.paged {
+            if page == 0 {
+              page = pages - 1;
+            } else {
+              page -= 1;
+            }
+
+            sel = page * capacity;
+          }
+        }
+        Key::ArrowRight | Key::Char('l') => {
+          if self.paged {
+            if page == pages - 1 {
+              page = 0;
+            } else {
+              page += 1;
+            }
+
+            sel = page * capacity;
+          }
+        }
+
+        Key::Enter | Key::Char(' ') if sel != !0 => {
+          if self.clear {
+            render.clear()?;
+          }
+
+          if let Some(ref prompt) = self.prompt {
+            render.select_prompt_selection(prompt, &self.items[sel])?;
+          }
+
+          term.show_cursor()?;
+          term.flush()?;
+
+          return Ok(Some(sel));
+        }
+        _ => {}
+      }
+
+      if sel != !0 && (sel < page * capacity || sel >= (page + 1) * capacity) {
+        page = sel / capacity;
+      }
+
+      render.clear_preserve_prompt(&size_vec)?;
+    }
+  }
+}
+
+#[cfg(test)]
+mod tests {
+  use super::*;
+
+  #[test]
+  fn test_str() {
+    let selections = &[
+      "Ice Cream",
+      "Vanilla Cupcake",
+      "Chocolate Muffin",
+      "A Pile of sweet, sweet mustard",
+    ];
+
+    assert_eq!(
+      Select::new().default(0).items(&selections[..]).items,
+      selections
+    );
+  }
+
+  #[test]
+  fn test_string() {
+    let selections = vec!["a".to_string(), "b".to_string()];
+
+    assert_eq!(
+      Select::new().default(0).items(&selections[..]).items,
+      selections
+    );
+  }
+
+  #[test]
+  fn test_ref_str() {
+    let a = "a";
+    let b = "b";
+
+    let selections = &[a, b];
+
+    assert_eq!(
+      Select::new().default(0).items(&selections[..]).items,
+      selections
+    );
+  }
+}

+ 269 - 0
tooling/cli.rs/src/dialoguer/prompts/sort.rs

@@ -0,0 +1,269 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::{io, ops::Rem};
+
+use super::super::theme::{SimpleTheme, TermThemeRenderer, Theme};
+
+use crate::console::{Key, Term};
+
+/// Renders a sort prompt.
+///
+/// Returns list of indices in original items list sorted according to user input.
+///
+/// ## Example usage
+/// ```rust,no_run
+/// use dialoguer::Sort;
+///
+/// # fn test() -> Result<(), Box<dyn std::error::Error>> {
+/// let items_to_order = vec!["Item 1", "Item 2", "Item 3"];
+/// let ordered = Sort::new()
+///     .with_prompt("Order the items")
+///     .items(&items_to_order)
+///     .interact()?;
+/// # Ok(())
+/// # }
+/// ```
+pub struct Sort<'a> {
+  items: Vec<String>,
+  prompt: Option<String>,
+  clear: bool,
+  theme: &'a dyn Theme,
+  paged: bool,
+}
+
+impl<'a> Default for Sort<'a> {
+  fn default() -> Sort<'a> {
+    Sort::new()
+  }
+}
+
+impl<'a> Sort<'a> {
+  /// Creates a sort prompt.
+  pub fn new() -> Sort<'static> {
+    Sort::with_theme(&SimpleTheme)
+  }
+
+  /// Creates a sort prompt with a specific theme.
+  pub fn with_theme(theme: &'a dyn Theme) -> Sort<'a> {
+    Sort {
+      items: vec![],
+      clear: true,
+      prompt: None,
+      theme,
+      paged: false,
+    }
+  }
+
+  /// Enables or disables paging
+  pub fn paged(&mut self, val: bool) -> &mut Sort<'a> {
+    self.paged = val;
+    self
+  }
+
+  /// Sets the clear behavior of the menu.
+  ///
+  /// The default is to clear the menu after user interaction.
+  pub fn clear(&mut self, val: bool) -> &mut Sort<'a> {
+    self.clear = val;
+    self
+  }
+
+  /// Add a single item to the selector.
+  pub fn item<T: ToString>(&mut self, item: T) -> &mut Sort<'a> {
+    self.items.push(item.to_string());
+    self
+  }
+
+  /// Adds multiple items to the selector.
+  pub fn items<T: ToString>(&mut self, items: &[T]) -> &mut Sort<'a> {
+    for item in items {
+      self.items.push(item.to_string());
+    }
+    self
+  }
+
+  /// Prefaces the menu with a prompt.
+  ///
+  /// When a prompt is set the system also prints out a confirmation after
+  /// the selection.
+  pub fn with_prompt<S: Into<String>>(&mut self, prompt: S) -> &mut Sort<'a> {
+    self.prompt = Some(prompt.into());
+    self
+  }
+
+  /// Enables user interaction and returns the result.
+  ///
+  /// The user can order the items with the space bar and the arrows.
+  /// On enter the ordered list will be returned.
+  pub fn interact(&self) -> io::Result<Vec<usize>> {
+    self.interact_on(&Term::stderr())
+  }
+
+  /// Like [interact](#method.interact) but allows a specific terminal to be set.
+  pub fn interact_on(&self, term: &Term) -> io::Result<Vec<usize>> {
+    let mut page = 0;
+
+    if self.items.is_empty() {
+      return Err(io::Error::new(
+        io::ErrorKind::Other,
+        "Empty list of items given to `Sort`",
+      ));
+    }
+
+    let capacity = if self.paged {
+      term.size().0 as usize - 1
+    } else {
+      self.items.len()
+    };
+
+    let pages = (self.items.len() as f64 / capacity as f64).ceil() as usize;
+
+    let mut render = TermThemeRenderer::new(term, self.theme);
+    let mut sel = 0;
+
+    if let Some(ref prompt) = self.prompt {
+      render.sort_prompt(prompt)?;
+    }
+
+    let mut size_vec = Vec::new();
+
+    for items in self.items.iter().as_slice() {
+      let size = &items.len();
+      size_vec.push(*size);
+    }
+
+    let mut order: Vec<_> = (0..self.items.len()).collect();
+    let mut checked: bool = false;
+
+    loop {
+      for (idx, item) in order
+        .iter()
+        .enumerate()
+        .skip(page * capacity)
+        .take(capacity)
+      {
+        render.sort_prompt_item(&self.items[*item], checked, sel == idx)?;
+      }
+
+      term.hide_cursor()?;
+      term.flush()?;
+
+      match term.read_key()? {
+        Key::ArrowDown | Key::Char('j') => {
+          let old_sel = sel;
+
+          if sel == !0 {
+            sel = 0;
+          } else {
+            sel = (sel as u64 + 1).rem(self.items.len() as u64) as usize;
+          }
+
+          if checked && old_sel != sel {
+            order.swap(old_sel, sel);
+          }
+        }
+        Key::ArrowUp | Key::Char('k') => {
+          let old_sel = sel;
+
+          if sel == !0 {
+            sel = self.items.len() - 1;
+          } else {
+            sel = ((sel as i64 - 1 + self.items.len() as i64) % (self.items.len() as i64)) as usize;
+          }
+
+          if checked && old_sel != sel {
+            order.swap(old_sel, sel);
+          }
+        }
+        Key::ArrowLeft | Key::Char('h') => {
+          if self.paged {
+            let old_sel = sel;
+            let old_page = page;
+
+            if page == 0 {
+              page = pages - 1;
+            } else {
+              page -= 1;
+            }
+
+            sel = page * capacity;
+
+            if checked {
+              let indexes: Vec<_> = if old_page == 0 {
+                let indexes1: Vec<_> = (0..=old_sel).rev().collect();
+                let indexes2: Vec<_> = (sel..self.items.len()).rev().collect();
+                [indexes1, indexes2].concat()
+              } else {
+                (sel..=old_sel).rev().collect()
+              };
+
+              for index in 0..(indexes.len() - 1) {
+                order.swap(indexes[index], indexes[index + 1]);
+              }
+            }
+          }
+        }
+        Key::ArrowRight | Key::Char('l') => {
+          if self.paged {
+            let old_sel = sel;
+            let old_page = page;
+
+            if page == pages - 1 {
+              page = 0;
+            } else {
+              page += 1;
+            }
+
+            sel = page * capacity;
+
+            if checked {
+              let indexes: Vec<_> = if old_page == pages - 1 {
+                let indexes1: Vec<_> = (old_sel..self.items.len()).collect();
+                let indexes2: Vec<_> = vec![0];
+                [indexes1, indexes2].concat()
+              } else {
+                (old_sel..=sel).collect()
+              };
+
+              for index in 0..(indexes.len() - 1) {
+                order.swap(indexes[index], indexes[index + 1]);
+              }
+            }
+          }
+        }
+        Key::Char(' ') => {
+          checked = !checked;
+        }
+        // TODO: Key::Escape
+        Key::Enter => {
+          if self.clear {
+            render.clear()?;
+          }
+
+          if let Some(ref prompt) = self.prompt {
+            let list: Vec<_> = order
+              .iter()
+              .enumerate()
+              .map(|(_, item)| self.items[*item].as_str())
+              .collect();
+            render.sort_prompt_selection(prompt, &list[..])?;
+          }
+
+          term.show_cursor()?;
+          term.flush()?;
+
+          return Ok(order);
+        }
+        _ => {}
+      }
+
+      if sel < page * capacity || sel >= (page + 1) * capacity {
+        page = sel / capacity;
+      }
+
+      render.clear_preserve_prompt(&size_vec)?;
+    }
+  }
+}

+ 722 - 0
tooling/cli.rs/src/dialoguer/theme.rs

@@ -0,0 +1,722 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+//! Customizes the rendering of the elements.
+use std::{fmt, io};
+
+use crate::console::{style, Style, StyledObject, Term};
+
+/// Implements a theme for dialoguer.
+pub trait Theme {
+  /// Formats a prompt.
+  #[inline]
+  fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
+    write!(f, "{}:", prompt)
+  }
+
+  /// Formats out an error.
+  #[inline]
+  fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
+    write!(f, "error: {}", err)
+  }
+
+  /// Formats a confirm prompt.
+  fn format_confirm_prompt(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    default: Option<bool>,
+  ) -> fmt::Result {
+    if !prompt.is_empty() {
+      write!(f, "{} ", &prompt)?;
+    }
+    match default {
+      None => write!(f, "[y/n] ")?,
+      Some(true) => write!(f, "[Y/n] ")?,
+      Some(false) => write!(f, "[y/N] ")?,
+    }
+    Ok(())
+  }
+
+  /// Formats a confirm prompt after selection.
+  fn format_confirm_prompt_selection(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    selection: Option<bool>,
+  ) -> fmt::Result {
+    let selection = selection.map(|b| if b { "yes" } else { "no" });
+
+    match selection {
+      Some(selection) if prompt.is_empty() => {
+        write!(f, "{}", selection)
+      }
+      Some(selection) => {
+        write!(f, "{} {}", &prompt, selection)
+      }
+      None if prompt.is_empty() => Ok(()),
+      None => {
+        write!(f, "{}", &prompt)
+      }
+    }
+  }
+
+  /// Formats an input prompt.
+  fn format_input_prompt(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    default: Option<&str>,
+  ) -> fmt::Result {
+    match default {
+      Some(default) if prompt.is_empty() => write!(f, "[{}]: ", default),
+      Some(default) => write!(f, "{} [{}]: ", prompt, default),
+      None => write!(f, "{}: ", prompt),
+    }
+  }
+
+  /// Formats an input prompt after selection.
+  #[inline]
+  fn format_input_prompt_selection(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    sel: &str,
+  ) -> fmt::Result {
+    write!(f, "{}: {}", prompt, sel)
+  }
+
+  /// Formats a password prompt.
+  #[inline]
+  fn format_password_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
+    self.format_input_prompt(f, prompt, None)
+  }
+
+  /// Formats a password prompt after selection.
+  #[inline]
+  fn format_password_prompt_selection(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
+    self.format_input_prompt_selection(f, prompt, "[hidden]")
+  }
+
+  /// Formats a select prompt.
+  #[inline]
+  fn format_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
+    self.format_prompt(f, prompt)
+  }
+
+  /// Formats a select prompt after selection.
+  #[inline]
+  fn format_select_prompt_selection(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    sel: &str,
+  ) -> fmt::Result {
+    self.format_input_prompt_selection(f, prompt, sel)
+  }
+
+  /// Formats a multi select prompt.
+  #[inline]
+  fn format_multi_select_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
+    self.format_prompt(f, prompt)
+  }
+
+  /// Formats a sort prompt.
+  #[inline]
+  fn format_sort_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
+    self.format_prompt(f, prompt)
+  }
+
+  /// Formats a multi_select prompt after selection.
+  fn format_multi_select_prompt_selection(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    selections: &[&str],
+  ) -> fmt::Result {
+    write!(f, "{}: ", prompt)?;
+    for (idx, sel) in selections.iter().enumerate() {
+      write!(f, "{}{}", if idx == 0 { "" } else { ", " }, sel)?;
+    }
+    Ok(())
+  }
+
+  /// Formats a sort prompt after selection.
+  #[inline]
+  fn format_sort_prompt_selection(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    selections: &[&str],
+  ) -> fmt::Result {
+    self.format_multi_select_prompt_selection(f, prompt, selections)
+  }
+
+  /// Formats a select prompt item.
+  fn format_select_prompt_item(
+    &self,
+    f: &mut dyn fmt::Write,
+    text: &str,
+    active: bool,
+  ) -> fmt::Result {
+    write!(f, "{} {}", if active { ">" } else { " " }, text)
+  }
+
+  /// Formats a multi select prompt item.
+  fn format_multi_select_prompt_item(
+    &self,
+    f: &mut dyn fmt::Write,
+    text: &str,
+    checked: bool,
+    active: bool,
+  ) -> fmt::Result {
+    write!(
+      f,
+      "{} {}",
+      match (checked, active) {
+        (true, true) => "> [x]",
+        (true, false) => "  [x]",
+        (false, true) => "> [ ]",
+        (false, false) => "  [ ]",
+      },
+      text
+    )
+  }
+
+  /// Formats a sort prompt item.
+  fn format_sort_prompt_item(
+    &self,
+    f: &mut dyn fmt::Write,
+    text: &str,
+    picked: bool,
+    active: bool,
+  ) -> fmt::Result {
+    write!(
+      f,
+      "{} {}",
+      match (picked, active) {
+        (true, true) => "> [x]",
+        (false, true) => "> [ ]",
+        (_, false) => "  [ ]",
+      },
+      text
+    )
+  }
+}
+
+/// The default theme.
+pub struct SimpleTheme;
+
+impl Theme for SimpleTheme {}
+
+/// A colorful theme
+pub struct ColorfulTheme {
+  /// The style for default values
+  pub defaults_style: Style,
+  /// The style for prompt
+  pub prompt_style: Style,
+  /// Prompt prefix value and style
+  pub prompt_prefix: StyledObject<String>,
+  /// Prompt suffix value and style
+  pub prompt_suffix: StyledObject<String>,
+  /// Prompt on success prefix value and style
+  pub success_prefix: StyledObject<String>,
+  /// Prompt on success suffix value and style
+  pub success_suffix: StyledObject<String>,
+  /// Error prefix value and style
+  pub error_prefix: StyledObject<String>,
+  /// The style for error message
+  pub error_style: Style,
+  /// The style for hints
+  pub hint_style: Style,
+  /// The style for values on prompt success
+  pub values_style: Style,
+  /// The style for active items
+  pub active_item_style: Style,
+  /// The style for inactive items
+  pub inactive_item_style: Style,
+  /// Active item in select prefix value and style
+  pub active_item_prefix: StyledObject<String>,
+  /// Inctive item in select prefix value and style
+  pub inactive_item_prefix: StyledObject<String>,
+  /// Checked item in multi select prefix value and style
+  pub checked_item_prefix: StyledObject<String>,
+  /// Unchecked item in multi select prefix value and style
+  pub unchecked_item_prefix: StyledObject<String>,
+  /// Picked item in sort prefix value and style
+  pub picked_item_prefix: StyledObject<String>,
+  /// Unpicked item in sort prefix value and style
+  pub unpicked_item_prefix: StyledObject<String>,
+  /// Show the selections from certain prompts inline
+  pub inline_selections: bool,
+}
+
+impl Default for ColorfulTheme {
+  fn default() -> ColorfulTheme {
+    ColorfulTheme {
+      defaults_style: Style::new().for_stderr().cyan(),
+      prompt_style: Style::new().for_stderr().bold(),
+      prompt_prefix: style("?".to_string()).for_stderr().yellow(),
+      prompt_suffix: style("›".to_string()).for_stderr().black().bright(),
+      success_prefix: style("✔".to_string()).for_stderr().green(),
+      success_suffix: style("·".to_string()).for_stderr().black().bright(),
+      error_prefix: style("✘".to_string()).for_stderr().red(),
+      error_style: Style::new().for_stderr().red(),
+      hint_style: Style::new().for_stderr().black().bright(),
+      values_style: Style::new().for_stderr().green(),
+      active_item_style: Style::new().for_stderr().cyan(),
+      inactive_item_style: Style::new().for_stderr(),
+      active_item_prefix: style("❯".to_string()).for_stderr().green(),
+      inactive_item_prefix: style(" ".to_string()).for_stderr(),
+      checked_item_prefix: style("✔".to_string()).for_stderr().green(),
+      unchecked_item_prefix: style("✔".to_string()).for_stderr().black(),
+      picked_item_prefix: style("❯".to_string()).for_stderr().green(),
+      unpicked_item_prefix: style(" ".to_string()).for_stderr(),
+      inline_selections: true,
+    }
+  }
+}
+
+impl Theme for ColorfulTheme {
+  /// Formats a prompt.
+  fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
+    if !prompt.is_empty() {
+      write!(
+        f,
+        "{} {} ",
+        &self.prompt_prefix,
+        self.prompt_style.apply_to(prompt)
+      )?;
+    }
+
+    write!(f, "{}", &self.prompt_suffix)
+  }
+
+  /// Formats an error
+  fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
+    write!(
+      f,
+      "{} {}",
+      &self.error_prefix,
+      self.error_style.apply_to(err)
+    )
+  }
+
+  /// Formats an input prompt.
+  fn format_input_prompt(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    default: Option<&str>,
+  ) -> fmt::Result {
+    if !prompt.is_empty() {
+      write!(
+        f,
+        "{} {} ",
+        &self.prompt_prefix,
+        self.prompt_style.apply_to(prompt)
+      )?;
+    }
+
+    match default {
+      Some(default) => write!(
+        f,
+        "{} {} ",
+        self.hint_style.apply_to(&format!("({})", default)),
+        &self.prompt_suffix
+      ),
+      None => write!(f, "{} ", &self.prompt_suffix),
+    }
+  }
+
+  /// Formats a confirm prompt.
+  fn format_confirm_prompt(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    default: Option<bool>,
+  ) -> fmt::Result {
+    if !prompt.is_empty() {
+      write!(
+        f,
+        "{} {} ",
+        &self.prompt_prefix,
+        self.prompt_style.apply_to(prompt)
+      )?;
+    }
+
+    match default {
+      None => write!(
+        f,
+        "{} {}",
+        self.hint_style.apply_to("(y/n)"),
+        &self.prompt_suffix
+      ),
+      Some(true) => write!(
+        f,
+        "{} {} {}",
+        self.hint_style.apply_to("(y/n)"),
+        &self.prompt_suffix,
+        self.defaults_style.apply_to("yes")
+      ),
+      Some(false) => write!(
+        f,
+        "{} {} {}",
+        self.hint_style.apply_to("(y/n)"),
+        &self.prompt_suffix,
+        self.defaults_style.apply_to("no")
+      ),
+    }
+  }
+
+  /// Formats a confirm prompt after selection.
+  fn format_confirm_prompt_selection(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    selection: Option<bool>,
+  ) -> fmt::Result {
+    if !prompt.is_empty() {
+      write!(
+        f,
+        "{} {} ",
+        &self.success_prefix,
+        self.prompt_style.apply_to(prompt)
+      )?;
+    }
+    let selection = selection.map(|b| if b { "yes" } else { "no" });
+
+    match selection {
+      Some(selection) => {
+        write!(
+          f,
+          "{} {}",
+          &self.success_suffix,
+          self.values_style.apply_to(selection)
+        )
+      }
+      None => {
+        write!(f, "{}", &self.success_suffix)
+      }
+    }
+  }
+
+  /// Formats an input prompt after selection.
+  fn format_input_prompt_selection(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    sel: &str,
+  ) -> fmt::Result {
+    if !prompt.is_empty() {
+      write!(
+        f,
+        "{} {} ",
+        &self.success_prefix,
+        self.prompt_style.apply_to(prompt)
+      )?;
+    }
+
+    write!(
+      f,
+      "{} {}",
+      &self.success_suffix,
+      self.values_style.apply_to(sel)
+    )
+  }
+
+  /// Formats a password prompt after selection.
+  fn format_password_prompt_selection(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
+    self.format_input_prompt_selection(f, prompt, "********")
+  }
+
+  /// Formats a multi select prompt after selection.
+  fn format_multi_select_prompt_selection(
+    &self,
+    f: &mut dyn fmt::Write,
+    prompt: &str,
+    selections: &[&str],
+  ) -> fmt::Result {
+    if !prompt.is_empty() {
+      write!(
+        f,
+        "{} {} ",
+        &self.success_prefix,
+        self.prompt_style.apply_to(prompt)
+      )?;
+    }
+
+    write!(f, "{} ", &self.success_suffix)?;
+
+    if self.inline_selections {
+      for (idx, sel) in selections.iter().enumerate() {
+        write!(
+          f,
+          "{}{}",
+          if idx == 0 { "" } else { ", " },
+          self.values_style.apply_to(sel)
+        )?;
+      }
+    }
+
+    Ok(())
+  }
+
+  /// Formats a select prompt item.
+  fn format_select_prompt_item(
+    &self,
+    f: &mut dyn fmt::Write,
+    text: &str,
+    active: bool,
+  ) -> fmt::Result {
+    let details = match active {
+      true => (
+        &self.active_item_prefix,
+        self.active_item_style.apply_to(text),
+      ),
+      false => (
+        &self.inactive_item_prefix,
+        self.inactive_item_style.apply_to(text),
+      ),
+    };
+
+    write!(f, "{} {}", details.0, details.1)
+  }
+
+  /// Formats a multi select prompt item.
+  fn format_multi_select_prompt_item(
+    &self,
+    f: &mut dyn fmt::Write,
+    text: &str,
+    checked: bool,
+    active: bool,
+  ) -> fmt::Result {
+    let details = match (checked, active) {
+      (true, true) => (
+        &self.checked_item_prefix,
+        self.active_item_style.apply_to(text),
+      ),
+      (true, false) => (
+        &self.checked_item_prefix,
+        self.inactive_item_style.apply_to(text),
+      ),
+      (false, true) => (
+        &self.unchecked_item_prefix,
+        self.active_item_style.apply_to(text),
+      ),
+      (false, false) => (
+        &self.unchecked_item_prefix,
+        self.inactive_item_style.apply_to(text),
+      ),
+    };
+
+    write!(f, "{} {}", details.0, details.1)
+  }
+
+  /// Formats a sort prompt item.
+  fn format_sort_prompt_item(
+    &self,
+    f: &mut dyn fmt::Write,
+    text: &str,
+    picked: bool,
+    active: bool,
+  ) -> fmt::Result {
+    let details = match (picked, active) {
+      (true, true) => (
+        &self.picked_item_prefix,
+        self.active_item_style.apply_to(text),
+      ),
+      (false, true) => (
+        &self.unpicked_item_prefix,
+        self.active_item_style.apply_to(text),
+      ),
+      (_, false) => (
+        &self.unpicked_item_prefix,
+        self.inactive_item_style.apply_to(text),
+      ),
+    };
+
+    write!(f, "{} {}", details.0, details.1)
+  }
+}
+
+/// Helper struct to conveniently render a theme ot a term.
+pub(crate) struct TermThemeRenderer<'a> {
+  term: &'a Term,
+  theme: &'a dyn Theme,
+  height: usize,
+  prompt_height: usize,
+  prompts_reset_height: bool,
+}
+
+impl<'a> TermThemeRenderer<'a> {
+  pub fn new(term: &'a Term, theme: &'a dyn Theme) -> TermThemeRenderer<'a> {
+    TermThemeRenderer {
+      term,
+      theme,
+      height: 0,
+      prompt_height: 0,
+      prompts_reset_height: true,
+    }
+  }
+
+  pub fn set_prompts_reset_height(&mut self, val: bool) {
+    self.prompts_reset_height = val;
+  }
+
+  pub fn term(&self) -> &Term {
+    self.term
+  }
+
+  pub fn add_line(&mut self) {
+    self.height += 1;
+  }
+
+  fn write_formatted_str<F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result>(
+    &mut self,
+    f: F,
+  ) -> io::Result<()> {
+    let mut buf = String::new();
+    f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
+    self.height += buf.chars().filter(|&x| x == '\n').count();
+    self.term.write_str(&buf)
+  }
+
+  fn write_formatted_line<F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result>(
+    &mut self,
+    f: F,
+  ) -> io::Result<()> {
+    let mut buf = String::new();
+    f(self, &mut buf).map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
+    self.height += buf.chars().filter(|&x| x == '\n').count() + 1;
+    self.term.write_line(&buf)
+  }
+
+  fn write_formatted_prompt<
+    F: FnOnce(&mut TermThemeRenderer, &mut dyn fmt::Write) -> fmt::Result,
+  >(
+    &mut self,
+    f: F,
+  ) -> io::Result<()> {
+    self.write_formatted_line(f)?;
+    if self.prompts_reset_height {
+      self.prompt_height = self.height;
+      self.height = 0;
+    }
+    Ok(())
+  }
+
+  pub fn error(&mut self, err: &str) -> io::Result<()> {
+    self.write_formatted_line(|this, buf| this.theme.format_error(buf, err))
+  }
+
+  pub fn confirm_prompt(&mut self, prompt: &str, default: Option<bool>) -> io::Result<()> {
+    self.write_formatted_str(|this, buf| this.theme.format_confirm_prompt(buf, prompt, default))
+  }
+
+  pub fn confirm_prompt_selection(&mut self, prompt: &str, sel: Option<bool>) -> io::Result<()> {
+    self.write_formatted_prompt(|this, buf| {
+      this.theme.format_confirm_prompt_selection(buf, prompt, sel)
+    })
+  }
+
+  pub fn input_prompt(&mut self, prompt: &str, default: Option<&str>) -> io::Result<()> {
+    self.write_formatted_str(|this, buf| this.theme.format_input_prompt(buf, prompt, default))
+  }
+
+  pub fn input_prompt_selection(&mut self, prompt: &str, sel: &str) -> io::Result<()> {
+    self.write_formatted_prompt(|this, buf| {
+      this.theme.format_input_prompt_selection(buf, prompt, sel)
+    })
+  }
+
+  pub fn password_prompt(&mut self, prompt: &str) -> io::Result<()> {
+    self.write_formatted_str(|this, buf| {
+      write!(buf, "\r")?;
+      this.theme.format_password_prompt(buf, prompt)
+    })
+  }
+
+  pub fn password_prompt_selection(&mut self, prompt: &str) -> io::Result<()> {
+    self
+      .write_formatted_prompt(|this, buf| this.theme.format_password_prompt_selection(buf, prompt))
+  }
+
+  pub fn select_prompt(&mut self, prompt: &str) -> io::Result<()> {
+    self.write_formatted_prompt(|this, buf| this.theme.format_select_prompt(buf, prompt))
+  }
+
+  pub fn select_prompt_selection(&mut self, prompt: &str, sel: &str) -> io::Result<()> {
+    self.write_formatted_prompt(|this, buf| {
+      this.theme.format_select_prompt_selection(buf, prompt, sel)
+    })
+  }
+
+  pub fn select_prompt_item(&mut self, text: &str, active: bool) -> io::Result<()> {
+    self.write_formatted_line(|this, buf| this.theme.format_select_prompt_item(buf, text, active))
+  }
+
+  pub fn multi_select_prompt(&mut self, prompt: &str) -> io::Result<()> {
+    self.write_formatted_prompt(|this, buf| this.theme.format_multi_select_prompt(buf, prompt))
+  }
+
+  pub fn multi_select_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> io::Result<()> {
+    self.write_formatted_prompt(|this, buf| {
+      this
+        .theme
+        .format_multi_select_prompt_selection(buf, prompt, sel)
+    })
+  }
+
+  pub fn multi_select_prompt_item(
+    &mut self,
+    text: &str,
+    checked: bool,
+    active: bool,
+  ) -> io::Result<()> {
+    self.write_formatted_line(|this, buf| {
+      this
+        .theme
+        .format_multi_select_prompt_item(buf, text, checked, active)
+    })
+  }
+
+  pub fn sort_prompt(&mut self, prompt: &str) -> io::Result<()> {
+    self.write_formatted_prompt(|this, buf| this.theme.format_sort_prompt(buf, prompt))
+  }
+
+  pub fn sort_prompt_selection(&mut self, prompt: &str, sel: &[&str]) -> io::Result<()> {
+    self
+      .write_formatted_prompt(|this, buf| this.theme.format_sort_prompt_selection(buf, prompt, sel))
+  }
+
+  pub fn sort_prompt_item(&mut self, text: &str, picked: bool, active: bool) -> io::Result<()> {
+    self.write_formatted_line(|this, buf| {
+      this
+        .theme
+        .format_sort_prompt_item(buf, text, picked, active)
+    })
+  }
+
+  pub fn clear(&mut self) -> io::Result<()> {
+    self
+      .term
+      .clear_last_lines(self.height + self.prompt_height)?;
+    self.height = 0;
+    Ok(())
+  }
+
+  pub fn clear_preserve_prompt(&mut self, size_vec: &[usize]) -> io::Result<()> {
+    let mut new_height = self.height;
+    //Check each item size, increment on finding an overflow
+    for size in size_vec {
+      if *size > self.term.size().1 as usize {
+        new_height += 1;
+      }
+    }
+    self.term.clear_last_lines(new_height)?;
+    self.height = 0;
+    Ok(())
+  }
+}

+ 28 - 0
tooling/cli.rs/src/dialoguer/validate.rs

@@ -0,0 +1,28 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+//! Provides validation for text inputs
+use std::fmt::{Debug, Display};
+
+/// Trait for input validators.
+///
+/// A generic implementation for `Fn(&str) -> Result<(), E>` is provided
+/// to facilitate development.
+pub trait Validator<T> {
+  type Err: Debug + Display;
+
+  /// Invoked with the value to validate.
+  ///
+  /// If this produces `Ok(())` then the value is used and parsed, if
+  /// an error is returned validation fails with that error.
+  fn validate(&mut self, input: &T) -> Result<(), Self::Err>;
+}
+
+impl<T, F: FnMut(&T) -> Result<(), E>, E: Debug + Display> Validator<T> for F {
+  type Err = E;
+
+  fn validate(&mut self, input: &T) -> Result<(), Self::Err> {
+    self(input)
+  }
+}

+ 7 - 0
tooling/cli.rs/src/main.rs

@@ -13,6 +13,13 @@ mod info;
 mod init;
 mod sign;
 
+// temporary fork from https://github.com/mitsuhiko/console until 0.14.1+ release
+#[allow(dead_code)]
+mod console;
+// temporary fork from https://github.com/mitsuhiko/dialoguer until 0.8.0+ release
+#[allow(dead_code)]
+mod dialoguer;
+
 pub use helpers::Logger;
 
 macro_rules! value_or_prompt {