* nu-completer with suggestions

* help menu with scrolling

* updates description rows based on space

* configuration for help menu

* update nu-ansi-term

* corrected test for update cells

* changed keybinding
This commit is contained in:
Fernando Herrera 2022-03-27 14:01:04 +01:00 committed by GitHub
parent 0011f4df56
commit a4410fef40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1152 additions and 192 deletions

154
Cargo.lock generated
View File

@ -182,9 +182,9 @@ dependencies = [
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
dependencies = [ dependencies = [
"async-stream-impl", "async-stream-impl",
"futures-core", "futures-core",
@ -192,9 +192,9 @@ dependencies = [
[[package]] [[package]]
name = "async-stream-impl" name = "async-stream-impl"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -203,9 +203,9 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.52" version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -428,9 +428,9 @@ dependencies = [
[[package]] [[package]]
name = "capnp" name = "capnp"
version = "0.14.5" version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16c262726f68118392269a3f7a5546baf51dcfe5cb3c3f0957b502106bf1a065" checksum = "21d5d7da973146f1720672faa44f1523cc8f923636190ca1a931c7bc8834de68"
[[package]] [[package]]
name = "cc" name = "cc"
@ -568,9 +568,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -586,9 +586,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.2" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"crossbeam-utils", "crossbeam-utils",
@ -607,10 +607,11 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.9.7" version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
dependencies = [ dependencies = [
"autocfg",
"cfg-if", "cfg-if",
"crossbeam-utils", "crossbeam-utils",
"lazy_static", "lazy_static",
@ -620,9 +621,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.7" version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"lazy_static", "lazy_static",
@ -630,9 +631,9 @@ dependencies = [
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.23.0" version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77b75a27dc8d220f1f8521ea69cd55a34d720a200ebb3a624d9aa19193d3b432" checksum = "f1fd7173631a4e9e2ca8b32ae2fad58aab9843ea5aaf56642661937d87e28a3e"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"crossterm_winapi", "crossterm_winapi",
@ -731,9 +732,9 @@ dependencies = [
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.1.21" version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@ -832,9 +833,9 @@ dependencies = [
[[package]] [[package]]
name = "dirs-sys" name = "dirs-sys"
version = "0.3.6" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [ dependencies = [
"libc", "libc",
"redox_users", "redox_users",
@ -914,14 +915,14 @@ dependencies = [
"rustc_version", "rustc_version",
"toml", "toml",
"vswhom", "vswhom",
"winreg 0.10.1", "winreg",
] ]
[[package]] [[package]]
name = "eml-parser" name = "eml-parser"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "031fe36712cec8b81c5b76b555666ce855a4dfc2dcc35bb907046bf2ef545578" checksum = "43e6fc6e74658e477675b59e61e10e9722cb2b845b0e2834df60f979c865e821"
dependencies = [ dependencies = [
"regex", "regex",
] ]
@ -966,9 +967,9 @@ dependencies = [
[[package]] [[package]]
name = "erased-serde" name = "erased-serde"
version = "0.3.18" version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56047058e1ab118075ca22f9ecd737bcc961aa3566a3019cb71388afa280bd8a" checksum = "ad132dd8d0d0b546348d7d86cb3191aad14b34e5f979781fc005c80d4ac67ffd"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -1011,9 +1012,9 @@ dependencies = [
[[package]] [[package]]
name = "fd-lock" name = "fd-lock"
version = "3.0.4" version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02ecad9808e0596f8956d14f7fa868f996290bd01c8d7329d6e5bc2bb76adf8f" checksum = "46e245f4c8ec30c6415c56cb132c07e69e74f1942f6b4a4061da748b49f486ca"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"rustix", "rustix",
@ -1431,9 +1432,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "html5ever" name = "html5ever"
version = "0.25.1" version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
dependencies = [ dependencies = [
"log", "log",
"mac", "mac",
@ -1494,9 +1495,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.17" version = "0.14.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@ -1605,9 +1606,9 @@ dependencies = [
[[package]] [[package]]
name = "io-lifetimes" name = "io-lifetimes"
version = "0.5.3" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" checksum = "9448015e586b611e5d322f6703812bbca2f1e709d5773ecd38ddb4e3bb649504"
[[package]] [[package]]
name = "ipnet" name = "ipnet"
@ -1735,9 +1736,9 @@ dependencies = [
[[package]] [[package]]
name = "lexical-write-float" name = "lexical-write-float"
version = "0.8.3" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26f6202bff3d35ede41a6200227837468bb92e4ecdd437328b1055ed218fb855" checksum = "8a89ec1d062e481210c309b672f73a0567b7855f21e7d2fae636df44d12e97f9"
dependencies = [ dependencies = [
"lexical-util", "lexical-util",
"lexical-write-integer", "lexical-write-integer",
@ -1756,9 +1757,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.119" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
[[package]] [[package]]
name = "libgit2-sys" name = "libgit2-sys"
@ -1843,9 +1844,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -2020,14 +2021,15 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"miow", "miow",
"ntapi", "ntapi",
"wasi 0.11.0+wasi-snapshot-preview1",
"winapi", "winapi",
] ]
@ -2717,9 +2719,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "owo-colors" name = "owo-colors"
version = "3.2.0" version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20448fd678ec04e6ea15bbe0476874af65e98a01515d667aa49f1434dc44ebf4" checksum = "5e72e30578e0d0993c8ae20823dd9cff2bc5517d2f586a8aef462a581e8a03eb"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
@ -3111,9 +3113,9 @@ dependencies = [
[[package]] [[package]]
name = "pretty_assertions" name = "pretty_assertions"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50" checksum = "57c038cb5319b9c704bf9c227c261d275bfec0ad438118a2787ce47944fb228b"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"ctor", "ctor",
@ -3234,9 +3236,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.15" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -3405,27 +3407,28 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.11" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.0" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" checksum = "7776223e2696f1aa4c6b0170e83212f47296a00424305117d013dfe86fb0fe55"
dependencies = [ dependencies = [
"getrandom 0.2.5", "getrandom 0.2.5",
"redox_syscall", "redox_syscall",
"thiserror",
] ]
[[package]] [[package]]
name = "reedline" name = "reedline"
version = "0.3.1" version = "0.3.1"
source = "git+https://github.com/nushell/reedline#bc528de132e74594fdd5a9202cf32aee51e921e8" source = "git+https://github.com/nushell/reedline?branch=main#e982abf7e21dcb41e9e1d715f64259a4817bb1bc"
dependencies = [ dependencies = [
"chrono", "chrono",
"crossterm", "crossterm",
@ -3473,9 +3476,9 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.9" version = "0.11.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@ -3504,7 +3507,7 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"winreg 0.7.0", "winreg",
] ]
[[package]] [[package]]
@ -3598,9 +3601,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.33.4" version = "0.34.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef7ec6a44fba95d21fa522760c03c16ca5ee95cebb6e4ef579cab3e6d7ba6c06" checksum = "cd3cc851a13d30a34cb747ba2a0c5101a4b2e8b1677a29b213ee465365ea495e"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -4125,9 +4128,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.86" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4304,7 +4307,7 @@ dependencies = [
"bytes", "bytes",
"libc", "libc",
"memchr", "memchr",
"mio 0.8.0", "mio 0.8.2",
"num_cpus", "num_cpus",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
@ -4521,9 +4524,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8-width" name = "utf8-width"
version = "0.1.5" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
@ -4647,6 +4650,12 @@ version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.79" version = "0.2.79"
@ -4725,9 +4734,9 @@ dependencies = [
[[package]] [[package]]
name = "which" name = "which"
version = "4.2.4" version = "4.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
dependencies = [ dependencies = [
"either", "either",
"lazy_static", "lazy_static",
@ -4881,15 +4890,6 @@ version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
[[package]]
name = "winreg"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.10.1" version = "0.10.1"
@ -4916,9 +4916,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.5.3" version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317"
[[package]] [[package]]
name = "zip" name = "zip"

View File

@ -38,7 +38,7 @@ crossterm_winapi = "0.9.0"
ctrlc = "3.2.1" ctrlc = "3.2.1"
log = "0.4" log = "0.4"
miette = "4.1.0" miette = "4.1.0"
nu-ansi-term = "0.45.0" nu-ansi-term = "0.45.1"
nu-cli = { path="./crates/nu-cli", version = "0.60.1" } nu-cli = { path="./crates/nu-cli", version = "0.60.1" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.60.1" } nu-color-config = { path = "./crates/nu-color-config", version = "0.60.1" }
nu-command = { path="./crates/nu-command", version = "0.60.1" } nu-command = { path="./crates/nu-command", version = "0.60.1" }
@ -55,7 +55,8 @@ nu-term-grid = { path = "./crates/nu-term-grid", version = "0.60.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.60.1" } nu-utils = { path = "./crates/nu-utils", version = "0.60.1" }
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
rayon = "1.5.1" rayon = "1.5.1"
reedline = { git = "https://github.com/nushell/reedline" } #reedline = "0.3.0"
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
is_executable = "1.0.1" is_executable = "1.0.1"
[dev-dependencies] [dev-dependencies]

View File

@ -12,13 +12,16 @@ nu-path = { path = "../nu-path", version = "0.60.1" }
nu-parser = { path = "../nu-parser", version = "0.60.1" } nu-parser = { path = "../nu-parser", version = "0.60.1" }
nu-protocol = { path = "../nu-protocol", version = "0.60.1" } nu-protocol = { path = "../nu-protocol", version = "0.60.1" }
nu-utils = { path = "../nu-utils", version = "0.60.1" } nu-utils = { path = "../nu-utils", version = "0.60.1" }
nu-ansi-term = "0.45.0" nu-ansi-term = "0.45.1"
nu-color-config = { path = "../nu-color-config", version = "0.60.1" } nu-color-config = { path = "../nu-color-config", version = "0.60.1" }
crossterm = "0.23.0" crossterm = "0.23.0"
crossterm_winapi = "0.9.0" crossterm_winapi = "0.9.0"
miette = { version = "4.1.0", features = ["fancy"] } miette = { version = "4.1.0", features = ["fancy"] }
thiserror = "1.0.29" thiserror = "1.0.29"
reedline = { git = "https://github.com/nushell/reedline" } #reedline = "0.3.0"
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
log = "0.4" log = "0.4"
is_executable = "1.0.1" is_executable = "1.0.1"

View File

@ -5,7 +5,7 @@ use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
PipelineData, Span, Value, CONFIG_VARIABLE_ID, PipelineData, Span, Value, CONFIG_VARIABLE_ID,
}; };
use reedline::Completer; use reedline::{Completer, Suggestion};
const SEP: char = std::path::MAIN_SEPARATOR; const SEP: char = std::path::MAIN_SEPARATOR;
@ -83,46 +83,49 @@ impl NuCompleter {
prefix: &[u8], prefix: &[u8],
span: Span, span: Span,
offset: usize, offset: usize,
) -> Vec<(reedline::Span, String)> { ) -> Vec<Suggestion> {
let mut output = vec![]; let mut output = vec![];
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"]; let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
for builtin in builtins { for builtin in builtins {
if builtin.as_bytes().starts_with(prefix) { if builtin.as_bytes().starts_with(prefix) {
output.push(( output.push(Suggestion {
reedline::Span { value: builtin.to_string(),
description: None,
span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
builtin.to_string(), });
));
} }
} }
for scope in &working_set.delta.scope { for scope in &working_set.delta.scope {
for v in &scope.vars { for v in &scope.vars {
if v.0.starts_with(prefix) { if v.0.starts_with(prefix) {
output.push(( output.push(Suggestion {
reedline::Span { value: String::from_utf8_lossy(v.0).to_string(),
description: None,
span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
String::from_utf8_lossy(v.0).to_string(), });
));
} }
} }
} }
for scope in &self.engine_state.scope { for scope in &self.engine_state.scope {
for v in &scope.vars { for v in &scope.vars {
if v.0.starts_with(prefix) { if v.0.starts_with(prefix) {
output.push(( output.push(Suggestion {
reedline::Span { value: String::from_utf8_lossy(v.0).to_string(),
description: None,
span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
String::from_utf8_lossy(v.0).to_string(), });
));
} }
} }
} }
@ -138,34 +141,32 @@ impl NuCompleter {
span: Span, span: Span,
offset: usize, offset: usize,
find_externals: bool, find_externals: bool,
) -> Vec<(reedline::Span, String)> { ) -> Vec<Suggestion> {
let prefix = working_set.get_span_contents(span); let prefix = working_set.get_span_contents(span);
let results = working_set let results = working_set
.find_commands_by_prefix(prefix) .find_commands_by_prefix(prefix)
.into_iter() .into_iter()
.map(move |x| { .map(move |x| Suggestion {
( value: String::from_utf8_lossy(&x).to_string(),
reedline::Span { description: None,
span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
String::from_utf8_lossy(&x).to_string(),
)
}); });
let results_aliases = let results_aliases =
working_set working_set
.find_aliases_by_prefix(prefix) .find_aliases_by_prefix(prefix)
.into_iter() .into_iter()
.map(move |x| { .map(move |x| Suggestion {
( value: String::from_utf8_lossy(&x).to_string(),
reedline::Span { description: None,
span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
String::from_utf8_lossy(&x).to_string(),
)
}); });
let mut results = results.chain(results_aliases).collect::<Vec<_>>(); let mut results = results.chain(results_aliases).collect::<Vec<_>>();
@ -176,19 +177,22 @@ impl NuCompleter {
let results_external = let results_external =
self.external_command_completion(&prefix) self.external_command_completion(&prefix)
.into_iter() .into_iter()
.map(move |x| { .map(move |x| Suggestion {
( value: x,
reedline::Span { description: None,
span: reedline::Span {
start: span.start - offset, start: span.start - offset,
end: span.end - offset, end: span.end - offset,
}, },
x,
)
}); });
for external in results_external { for external in results_external {
if results.contains(&external) { if results.contains(&external) {
results.push((external.0, format!("^{}", external.1))) results.push(Suggestion {
value: format!("^{}", external.value),
description: None,
span: external.span,
})
} else { } else {
results.push(external) results.push(external)
} }
@ -200,7 +204,7 @@ impl NuCompleter {
} }
} }
fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { fn completion_helper(&self, line: &str, pos: usize) -> Vec<Suggestion> {
let mut working_set = StateWorkingSet::new(&self.engine_state); let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start(); let offset = working_set.next_span_start();
let mut line = line.to_string(); let mut line = line.to_string();
@ -231,7 +235,7 @@ impl NuCompleter {
if prefix.starts_with(b"$") { if prefix.starts_with(b"$") {
let mut output = let mut output =
self.complete_variables(&working_set, &prefix, new_span, offset); self.complete_variables(&working_set, &prefix, new_span, offset);
output.sort_by(|a, b| a.1.cmp(&b.1)); output.sort_by(|a, b| a.value.cmp(&b.value));
return output; return output;
} }
if prefix.starts_with(b"-") { if prefix.starts_with(b"-") {
@ -248,13 +252,14 @@ impl NuCompleter {
short.encode_utf8(&mut named); short.encode_utf8(&mut named);
named.insert(0, b'-'); named.insert(0, b'-');
if named.starts_with(&prefix) { if named.starts_with(&prefix) {
output.push(( output.push(Suggestion {
reedline::Span { value: String::from_utf8_lossy(&named).to_string(),
description: None,
span: reedline::Span {
start: new_span.start - offset, start: new_span.start - offset,
end: new_span.end - offset, end: new_span.end - offset,
}, },
String::from_utf8_lossy(&named).to_string(), });
));
} }
} }
@ -266,16 +271,17 @@ impl NuCompleter {
named.insert(0, b'-'); named.insert(0, b'-');
named.insert(0, b'-'); named.insert(0, b'-');
if named.starts_with(&prefix) { if named.starts_with(&prefix) {
output.push(( output.push(Suggestion {
reedline::Span { value: String::from_utf8_lossy(&named).to_string(),
description: None,
span: reedline::Span {
start: new_span.start - offset, start: new_span.start - offset,
end: new_span.end - offset, end: new_span.end - offset,
}, },
String::from_utf8_lossy(&named).to_string(), });
));
} }
} }
output.sort_by(|a, b| a.1.cmp(&b.1)); output.sort_by(|a, b| a.value.cmp(&b.value));
return output; return output;
} }
} }
@ -317,18 +323,19 @@ impl NuCompleter {
list: impl Iterator<Item = &'a Value>, list: impl Iterator<Item = &'a Value>,
new_span: Span, new_span: Span,
offset: usize, offset: usize,
) -> Vec<(reedline::Span, String)> { ) -> Vec<Suggestion> {
list.filter_map(move |x| { list.filter_map(move |x| {
let s = x.as_string(); let s = x.as_string();
match s { match s {
Ok(s) => Some(( Ok(s) => Some(Suggestion {
reedline::Span { value: s,
description: None,
span: reedline::Span {
start: new_span.start - offset, start: new_span.start - offset,
end: new_span.end - offset, end: new_span.end - offset,
}, },
s, }),
)),
Err(_) => None, Err(_) => None,
} }
}) })
@ -388,17 +395,19 @@ impl NuCompleter {
_ => (vec![], CompletionOptions::default()), _ => (vec![], CompletionOptions::default()),
}; };
let mut completions: Vec<(reedline::Span, String)> = completions let mut completions: Vec<Suggestion> = completions
.into_iter() .into_iter()
.filter(|it| { .filter(|it| {
// Minimise clones for new functionality // Minimise clones for new functionality
match (options.case_sensitive, options.positional) { match (options.case_sensitive, options.positional) {
(true, true) => it.1.as_bytes().starts_with(&prefix), (true, true) => {
(true, false) => it.1.contains( it.value.as_bytes().starts_with(&prefix)
}
(true, false) => it.value.contains(
std::str::from_utf8(&prefix).unwrap_or(""), std::str::from_utf8(&prefix).unwrap_or(""),
), ),
(false, positional) => { (false, positional) => {
let value = it.1.to_lowercase(); let value = it.value.to_lowercase();
let prefix = std::str::from_utf8(&prefix) let prefix = std::str::from_utf8(&prefix)
.unwrap_or("") .unwrap_or("")
.to_lowercase(); .to_lowercase();
@ -413,7 +422,7 @@ impl NuCompleter {
.collect(); .collect();
if options.sort { if options.sort {
completions.sort_by(|a, b| a.1.cmp(&b.1)); completions.sort_by(|a, b| a.value.cmp(&b.value));
} }
return completions; return completions;
@ -431,17 +440,16 @@ impl NuCompleter {
let mut output: Vec<_> = let mut output: Vec<_> =
file_path_completion(new_span, &prefix, &cwd) file_path_completion(new_span, &prefix, &cwd)
.into_iter() .into_iter()
.map(move |x| { .map(move |x| Suggestion {
( value: x.1,
reedline::Span { description: None,
span: reedline::Span {
start: x.0.start - offset, start: x.0.start - offset,
end: x.0.end - offset, end: x.0.end - offset,
}, },
x.1,
)
}) })
.collect(); .collect();
output.sort_by(|a, b| a.1.cmp(&b.1)); output.sort_by(|a, b| a.value.cmp(&b.value));
return output; return output;
} }
flat_shape => { flat_shape => {
@ -542,20 +550,19 @@ impl NuCompleter {
(x.0, x.1) (x.0, x.1)
} }
}) })
.map(move |x| { .map(move |x| Suggestion {
( value: x.1,
reedline::Span { description: None,
span: reedline::Span {
start: x.0.start - offset, start: x.0.start - offset,
end: x.0.end - offset, end: x.0.end - offset,
}, },
x.1,
)
}) })
.chain(subcommands.into_iter()) .chain(subcommands.into_iter())
.chain(commands.into_iter()) .chain(commands.into_iter())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
//output.dedup_by(|a, b| a.1 == b.1); //output.dedup_by(|a, b| a.1 == b.1);
output.sort_by(|a, b| a.1.cmp(&b.1)); output.sort_by(|a, b| a.value.cmp(&b.value));
return output; return output;
} }
@ -570,7 +577,7 @@ impl NuCompleter {
} }
impl Completer for NuCompleter { impl Completer for NuCompleter {
fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { fn complete(&self, line: &str, pos: usize) -> Vec<Suggestion> {
self.completion_helper(line, pos) self.completion_helper(line, pos)
} }
} }

View File

@ -0,0 +1,101 @@
use nu_engine::documentation::get_flags_section;
use nu_protocol::engine::EngineState;
use reedline::{Completer, Suggestion};
pub const EXAMPLE_MARKER: &str = ">>>>>>";
pub const EXAMPLE_NEW_LINE: &str = "%%%%%%";
pub struct NuHelpCompleter {
engine_state: EngineState,
}
impl NuHelpCompleter {
pub fn new(engine_state: EngineState) -> Self {
Self { engine_state }
}
fn completion_helper(&self, line: &str, _pos: usize) -> Vec<Suggestion> {
let full_commands = self.engine_state.get_signatures_with_examples(false);
//Vec<(Signature, Vec<Example>, bool, bool)> {
full_commands
.iter()
.filter(|(sig, _, _, _)| {
sig.name.to_lowercase().contains(&line.to_lowercase())
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|| sig
.extra_usage
.to_lowercase()
.contains(&line.to_lowercase())
})
.map(|(sig, examples, _, _)| {
let mut long_desc = String::new();
let usage = &sig.usage;
if !usage.is_empty() {
long_desc.push_str(usage);
long_desc.push_str("\r\n\r\n");
}
let extra_usage = &sig.extra_usage;
if !extra_usage.is_empty() {
long_desc.push_str(extra_usage);
long_desc.push_str("\r\n\r\n");
}
long_desc.push_str(&format!("Usage:\r\n > {}\r\n", sig.call_signature()));
if !sig.named.is_empty() {
long_desc.push_str(&get_flags_section(sig))
}
if !sig.required_positional.is_empty()
|| !sig.optional_positional.is_empty()
|| sig.rest_positional.is_some()
{
long_desc.push_str("\r\nParameters:\r\n");
for positional in &sig.required_positional {
long_desc
.push_str(&format!(" {}: {}\r\n", positional.name, positional.desc));
}
for positional in &sig.optional_positional {
long_desc.push_str(&format!(
" (optional) {}: {}\r\n",
positional.name, positional.desc
));
}
if let Some(rest_positional) = &sig.rest_positional {
long_desc.push_str(&format!(
" ...{}: {}\r\n",
rest_positional.name, rest_positional.desc
));
}
}
for example in examples {
long_desc.push_str(&format!(
"{}{}\r\n",
EXAMPLE_MARKER,
example.example.replace('\n', EXAMPLE_NEW_LINE)
))
}
Suggestion {
value: sig.name.clone(),
description: Some(long_desc),
span: reedline::Span {
start: 0,
end: sig.name.len(),
},
}
})
.collect()
}
}
impl Completer for NuHelpCompleter {
fn complete(&self, line: &str, pos: usize) -> Vec<Suggestion> {
self.completion_helper(line, pos)
}
}

View File

@ -0,0 +1,718 @@
use {
crate::help_completions::{EXAMPLE_MARKER, EXAMPLE_NEW_LINE},
nu_ansi_term::{ansi::RESET, Style},
reedline::{
menu_functions::string_difference, Completer, History, LineBuffer, Menu, MenuEvent,
MenuTextStyle, Painter, Suggestion,
},
};
/// Default values used as reference for the menu. These values are set during
/// the initial declaration of the menu and are always kept as reference for the
/// changeable [`WorkingDetails`]
struct DefaultMenuDetails {
/// Number of columns that the menu will have
pub columns: u16,
/// Column width
pub col_width: Option<usize>,
/// Column padding
pub col_padding: usize,
/// Number of rows for commands
pub selection_rows: u16,
/// Number of rows allowed to display the description
pub description_rows: usize,
}
impl Default for DefaultMenuDetails {
fn default() -> Self {
Self {
columns: 4,
col_width: None,
col_padding: 2,
selection_rows: 4,
description_rows: 10,
}
}
}
/// Represents the actual column conditions of the menu. These conditions change
/// since they need to accommodate possible different line sizes for the column values
#[derive(Default)]
struct WorkingDetails {
/// Number of columns that the menu will have
pub columns: u16,
/// Column width
pub col_width: usize,
/// Number of rows for description
pub description_rows: usize,
}
/// Completion menu definition
pub struct NuHelpMenu {
active: bool,
/// Menu coloring
color: MenuTextStyle,
/// Default column details that are set when creating the menu
/// These values are the reference for the working details
default_details: DefaultMenuDetails,
/// Number of minimum rows that are displayed when
/// the required lines is larger than the available lines
min_rows: u16,
/// Working column details keep changing based on the collected values
working_details: WorkingDetails,
/// Menu cached values
values: Vec<Suggestion>,
/// column position of the cursor. Starts from 0
col_pos: u16,
/// row position in the menu. Starts from 0
row_pos: u16,
/// Menu marker when active
marker: String,
/// Event sent to the menu
event: Option<MenuEvent>,
/// String collected after the menu is activated
input: Option<String>,
/// Examples to select
examples: Vec<String>,
/// Example index
example_index: Option<usize>,
/// Examples may not be shown if there is not enough space in the screen
show_examples: bool,
/// Skipped description rows
skipped_rows: usize,
}
impl Default for NuHelpMenu {
fn default() -> Self {
Self {
active: false,
color: MenuTextStyle::default(),
default_details: DefaultMenuDetails::default(),
min_rows: 3,
working_details: WorkingDetails::default(),
values: Vec::new(),
col_pos: 0,
row_pos: 0,
marker: "| ".to_string(),
event: None,
input: None,
examples: Vec::new(),
example_index: None,
show_examples: true,
skipped_rows: 0,
}
}
}
impl NuHelpMenu {
/// Menu builder with new value for text style
pub fn with_text_style(mut self, text_style: Style) -> Self {
self.color.text_style = text_style;
self
}
/// Menu builder with new value for text style
pub fn with_selected_text_style(mut self, selected_text_style: Style) -> Self {
self.color.selected_text_style = selected_text_style;
self
}
/// Menu builder with new value for text style
pub fn with_description_text_style(mut self, description_text_style: Style) -> Self {
self.color.description_style = description_text_style;
self
}
/// Menu builder with new columns value
pub fn with_columns(mut self, columns: u16) -> Self {
self.default_details.columns = columns;
self
}
/// Menu builder with new column width value
pub fn with_column_width(mut self, col_width: Option<usize>) -> Self {
self.default_details.col_width = col_width;
self
}
/// Menu builder with new column width value
pub fn with_column_padding(mut self, col_padding: usize) -> Self {
self.default_details.col_padding = col_padding;
self
}
/// Menu builder with new selection rows value
pub fn with_selection_rows(mut self, selection_rows: u16) -> Self {
self.default_details.selection_rows = selection_rows;
self
}
/// Menu builder with new description rows value
pub fn with_description_rows(mut self, description_rows: usize) -> Self {
self.default_details.description_rows = description_rows;
self
}
/// Menu builder with marker
pub fn with_marker(mut self, marker: String) -> Self {
self.marker = marker;
self
}
/// Move menu cursor to the next element
fn move_next(&mut self) {
let mut new_col = self.col_pos + 1;
let mut new_row = self.row_pos;
if new_col >= self.get_cols() {
new_row += 1;
new_col = 0;
}
if new_row >= self.get_rows() {
new_row = 0;
new_col = 0;
}
let position = new_row * self.get_cols() + new_col;
if position >= self.get_values().len() as u16 {
self.reset_position();
} else {
self.col_pos = new_col;
self.row_pos = new_row;
}
}
/// Move menu cursor to the previous element
fn move_previous(&mut self) {
let new_col = self.col_pos.checked_sub(1);
let (new_col, new_row) = match new_col {
Some(col) => (col, self.row_pos),
None => match self.row_pos.checked_sub(1) {
Some(row) => (self.get_cols().saturating_sub(1), row),
None => (
self.get_cols().saturating_sub(1),
self.get_rows().saturating_sub(1),
),
},
};
let position = new_row * self.get_cols() + new_col;
if position >= self.get_values().len() as u16 {
self.col_pos = (self.get_values().len() as u16 % self.get_cols()).saturating_sub(1);
self.row_pos = self.get_rows().saturating_sub(1);
} else {
self.col_pos = new_col;
self.row_pos = new_row;
}
}
/// Menu index based on column and row position
fn index(&self) -> usize {
let index = self.row_pos * self.get_cols() + self.col_pos;
index as usize
}
/// Get selected value from the menu
fn get_value(&self) -> Option<Suggestion> {
self.get_values().get(self.index()).cloned()
}
/// Calculates how many rows the Menu will use
fn get_rows(&self) -> u16 {
let values = self.get_values().len() as u16;
if values == 0 {
// When the values are empty the no_records_msg is shown, taking 1 line
return 1;
}
let rows = values / self.get_cols();
if values % self.get_cols() != 0 {
rows + 1
} else {
rows
}
}
/// Returns working details col width
fn get_width(&self) -> usize {
self.working_details.col_width
}
/// Reset menu position
fn reset_position(&mut self) {
self.col_pos = 0;
self.row_pos = 0;
self.skipped_rows = 0;
}
fn no_records_msg(&self, use_ansi_coloring: bool) -> String {
let msg = "TYPE TO START SEACH";
if use_ansi_coloring {
format!(
"{}{}{}",
self.color.selected_text_style.prefix(),
msg,
RESET
)
} else {
msg.to_string()
}
}
/// Returns working details columns
fn get_cols(&self) -> u16 {
self.working_details.columns.max(1)
}
/// End of line for menu
fn end_of_line(&self, column: u16, index: usize) -> &str {
let is_last = index == self.values.len().saturating_sub(1);
if column == self.get_cols().saturating_sub(1) || is_last {
"\r\n"
} else {
""
}
}
/// Update list of examples from the actual value
fn update_examples(&mut self) {
let examples = self
.get_value()
.and_then(|suggestion| suggestion.description)
.unwrap_or_else(|| "".to_string())
.lines()
.filter(|line| line.starts_with(EXAMPLE_MARKER))
.map(|line| {
line.replace(EXAMPLE_MARKER, "")
.replace(EXAMPLE_NEW_LINE, "\r\n")
})
.collect::<Vec<String>>();
self.examples = examples;
self.example_index = None;
}
/// Creates default string that represents one suggestion from the menu
fn create_entry_string(
&self,
suggestion: &Suggestion,
index: usize,
column: u16,
empty_space: usize,
use_ansi_coloring: bool,
) -> String {
if use_ansi_coloring {
if index == self.index() {
format!(
"{}{}{:>empty$}{}{}",
self.color.selected_text_style.prefix(),
&suggestion.value,
"",
RESET,
self.end_of_line(column, index),
empty = empty_space,
)
} else {
format!(
"{}{}{:>empty$}{}{}",
self.color.text_style.prefix(),
&suggestion.value,
"",
RESET,
self.end_of_line(column, index),
empty = empty_space,
)
}
} else {
// If no ansi coloring is found, then the selection word is
// the line in uppercase
let (marker, empty_space) = if index == self.index() {
(">", empty_space.saturating_sub(1))
} else {
("", empty_space)
};
let line = format!(
"{}{}{:>empty$}{}",
marker,
&suggestion.value,
"",
self.end_of_line(column, index),
empty = empty_space,
);
if index == self.index() {
line.to_uppercase()
} else {
line
}
}
}
/// Description string with color
fn create_description_string(&self, use_ansi_coloring: bool) -> String {
let description = self
.get_value()
.and_then(|suggestion| suggestion.description)
.unwrap_or_else(|| "".to_string())
.lines()
.filter(|line| !line.starts_with(EXAMPLE_MARKER))
.skip(self.skipped_rows)
.take(self.working_details.description_rows)
.collect::<Vec<&str>>()
.join("\r\n");
if use_ansi_coloring && !description.is_empty() {
format!(
"{}{}{}",
self.color.description_style.prefix(),
description,
RESET,
)
} else {
description
}
}
/// Selectable list of examples from the actual value
fn create_example_string(&self, use_ansi_coloring: bool) -> String {
if !self.show_examples {
return "".into();
}
let examples: String = self
.examples
.iter()
.enumerate()
.map(|(index, example)| {
if let Some(example_index) = self.example_index {
if index == example_index {
format!(
" {}{}{}\r\n",
self.color.selected_text_style.prefix(),
example,
RESET
)
} else {
format!(" {}\r\n", example)
}
} else {
format!(" {}\r\n", example)
}
})
.collect();
if examples.is_empty() {
"".into()
} else if use_ansi_coloring {
format!(
"{}\r\n\r\nExamples:\r\n{}{}",
self.color.description_style.prefix(),
RESET,
examples,
)
} else {
format!("\r\n\r\nExamples:\r\n{}", examples,)
}
}
}
impl Menu for NuHelpMenu {
/// Menu name
fn name(&self) -> &str {
"help_menu"
}
/// Menu indicator
fn indicator(&self) -> &str {
self.marker.as_str()
}
/// Deactivates context menu
fn is_active(&self) -> bool {
self.active
}
/// The help menu stays active even with one record
fn can_quick_complete(&self) -> bool {
false
}
/// The help menu does not need to partially complete
fn can_partially_complete(
&mut self,
_values_updated: bool,
_line_buffer: &mut LineBuffer,
_history: &dyn History,
_completer: &dyn Completer,
) -> bool {
false
}
/// Selects what type of event happened with the menu
fn menu_event(&mut self, event: MenuEvent) {
match &event {
MenuEvent::Activate(_) => self.active = true,
MenuEvent::Deactivate => {
self.active = false;
self.input = None;
self.values = Vec::new();
}
_ => {}
};
self.event = Some(event);
}
/// Updates menu values
fn update_values(
&mut self,
line_buffer: &mut LineBuffer,
_history: &dyn History,
completer: &dyn Completer,
) {
if let Some(old_string) = &self.input {
let (start, input) = string_difference(line_buffer.get_buffer(), old_string);
if !input.is_empty() {
self.reset_position();
self.values = completer
.complete(input, line_buffer.insertion_point())
.into_iter()
.map(|suggestion| Suggestion {
value: suggestion.value,
description: suggestion.description,
span: reedline::Span {
start,
end: start + input.len(),
},
})
.collect();
}
}
}
/// The working details for the menu changes based on the size of the lines
/// collected from the completer
fn update_working_details(
&mut self,
line_buffer: &mut LineBuffer,
history: &dyn History,
completer: &dyn Completer,
painter: &Painter,
) {
if let Some(event) = self.event.take() {
// Updating all working parameters from the menu before executing any of the
// possible event
let max_width = self.get_values().iter().fold(0, |acc, suggestion| {
let str_len = suggestion.value.len() + self.default_details.col_padding;
if str_len > acc {
str_len
} else {
acc
}
});
// If no default width is found, then the total screen width is used to estimate
// the column width based on the default number of columns
let default_width = if let Some(col_width) = self.default_details.col_width {
col_width
} else {
let col_width = painter.screen_width() / self.default_details.columns;
col_width as usize
};
// Adjusting the working width of the column based the max line width found
// in the menu values
if max_width > default_width {
self.working_details.col_width = max_width;
} else {
self.working_details.col_width = default_width;
};
// The working columns is adjusted based on possible number of columns
// that could be fitted in the screen with the calculated column width
let possible_cols = painter.screen_width() / self.working_details.col_width as u16;
if possible_cols > self.default_details.columns {
self.working_details.columns = self.default_details.columns.max(1);
} else {
self.working_details.columns = possible_cols;
}
// Updating the working rows to display the description
if self.menu_required_lines(painter.screen_width()) <= painter.remaining_lines() {
self.working_details.description_rows = self.default_details.description_rows;
self.show_examples = true;
} else {
self.working_details.description_rows = painter
.remaining_lines()
.saturating_sub(self.default_details.selection_rows + 1)
as usize;
self.show_examples = false;
}
match event {
MenuEvent::Activate(_) => {
self.reset_position();
self.input = Some(line_buffer.get_buffer().to_string());
self.update_values(line_buffer, history, completer);
}
MenuEvent::Deactivate => self.active = false,
MenuEvent::Edit(_) => {
self.reset_position();
self.update_values(line_buffer, history, completer);
self.update_examples()
}
MenuEvent::NextElement => {
self.skipped_rows = 0;
self.move_next();
self.update_examples();
}
MenuEvent::PreviousElement => {
self.skipped_rows = 0;
self.move_previous();
self.update_examples();
}
MenuEvent::MoveUp => {
if let Some(example_index) = self.example_index {
if let Some(index) = example_index.checked_sub(1) {
self.example_index = Some(index);
} else {
self.example_index = Some(self.examples.len().saturating_sub(1));
}
} else {
self.example_index = Some(0);
}
}
MenuEvent::MoveDown => {
if let Some(example_index) = self.example_index {
let index = example_index + 1;
if index < self.examples.len() {
self.example_index = Some(index);
} else {
self.example_index = Some(0);
}
} else {
self.example_index = Some(0);
}
}
MenuEvent::MoveLeft => self.skipped_rows = self.skipped_rows.saturating_sub(1),
MenuEvent::MoveRight => {
let skipped = self.skipped_rows + 1;
let description_rows = self
.get_value()
.and_then(|suggestion| suggestion.description)
.unwrap_or_else(|| "".to_string())
.lines()
.filter(|line| !line.starts_with(EXAMPLE_MARKER))
.count();
let allowed_skips =
description_rows.saturating_sub(self.working_details.description_rows);
if skipped < allowed_skips {
self.skipped_rows = skipped;
} else {
self.skipped_rows = allowed_skips;
}
}
MenuEvent::PreviousPage | MenuEvent::NextPage => {}
}
}
}
/// The buffer gets replaced in the Span location
fn replace_in_buffer(&self, line_buffer: &mut LineBuffer) {
if let Some(Suggestion { value, span, .. }) = self.get_value() {
let string_len = if let Some(example_index) = self.example_index {
let example = self
.examples
.get(example_index)
.expect("the example index is always checked");
line_buffer.replace(span.start..span.end, example);
example.len()
} else {
line_buffer.replace(span.start..span.end, &value);
value.len()
};
let mut offset = line_buffer.insertion_point();
offset += string_len.saturating_sub(span.end - span.start);
line_buffer.set_insertion_point(offset);
}
}
/// Minimum rows that should be displayed by the menu
fn min_rows(&self) -> u16 {
self.get_rows().min(self.min_rows)
}
/// Gets values from filler that will be displayed in the menu
fn get_values(&self) -> &[Suggestion] {
&self.values
}
fn menu_required_lines(&self, _terminal_columns: u16) -> u16 {
let example_lines = self
.examples
.iter()
.fold(0, |acc, example| example.lines().count() + acc);
self.default_details.selection_rows
+ self.default_details.description_rows as u16
+ example_lines as u16
+ 3
}
fn menu_string(&self, _available_lines: u16, use_ansi_coloring: bool) -> String {
if self.get_values().is_empty() {
self.no_records_msg(use_ansi_coloring)
} else {
// The skip values represent the number of lines that should be skipped
// while printing the menu
let available_lines = self.default_details.selection_rows;
let skip_values = if self.row_pos >= available_lines {
let skip_lines = self.row_pos.saturating_sub(available_lines) + 1;
(skip_lines * self.get_cols()) as usize
} else {
0
};
// It seems that crossterm prefers to have a complete string ready to be printed
// rather than looping through the values and printing multiple things
// This reduces the flickering when printing the menu
let available_values = (available_lines * self.get_cols()) as usize;
let selection_values: String = self
.get_values()
.iter()
.skip(skip_values)
.take(available_values)
.enumerate()
.map(|(index, suggestion)| {
// Correcting the enumerate index based on the number of skipped values
let index = index + skip_values;
let column = index as u16 % self.get_cols();
let empty_space = self.get_width().saturating_sub(suggestion.value.len());
self.create_entry_string(
suggestion,
index,
column,
empty_space,
use_ansi_coloring,
)
})
.collect();
format!(
"{}{}{}",
selection_values,
self.create_description_string(use_ansi_coloring),
self.create_example_string(use_ansi_coloring)
)
}
}
}

View File

@ -3,6 +3,8 @@ mod completions;
mod config_files; mod config_files;
mod errors; mod errors;
mod eval_file; mod eval_file;
mod help_completions;
mod help_menu;
mod nu_highlight; mod nu_highlight;
mod print; mod print;
mod prompt; mod prompt;
@ -18,6 +20,8 @@ pub use completions::NuCompleter;
pub use config_files::eval_config_contents; pub use config_files::eval_config_contents;
pub use errors::CliError; pub use errors::CliError;
pub use eval_file::evaluate_file; pub use eval_file::evaluate_file;
pub use help_completions::NuHelpCompleter;
pub use help_menu::NuHelpMenu;
pub use nu_highlight::NuHighlight; pub use nu_highlight::NuHighlight;
pub use print::Print; pub use print::Print;
pub use prompt::NushellPrompt; pub use prompt::NushellPrompt;

View File

@ -1,9 +1,10 @@
use super::NuHelpMenu;
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use nu_color_config::lookup_ansi_color_style; use nu_color_config::lookup_ansi_color_style;
use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Value}; use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Value};
use reedline::{ use reedline::{
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
CompletionMenu, EditCommand, HistoryMenu, Keybindings, Reedline, ReedlineEvent, Completer, CompletionMenu, EditCommand, HistoryMenu, Keybindings, Reedline, ReedlineEvent,
}; };
// Creates an input object for the completion menu based on the dictionary // Creates an input object for the completion menu based on the dictionary
@ -64,7 +65,7 @@ pub(crate) fn add_completion_menu(line_editor: Reedline, config: &Config) -> Ree
None => completion_menu, None => completion_menu,
}; };
line_editor.with_menu(Box::new(completion_menu)) line_editor.with_menu(Box::new(completion_menu), None)
} }
// Creates an input object for the history menu based on the dictionary // Creates an input object for the history menu based on the dictionary
@ -120,10 +121,119 @@ pub(crate) fn add_history_menu(line_editor: Reedline, config: &Config) -> Reedli
None => history_menu, None => history_menu,
}; };
line_editor.with_menu(Box::new(history_menu)) line_editor.with_menu(Box::new(history_menu), None)
}
// Creates an input object for the help menu based on the dictionary
// stored in the config variable
pub(crate) fn add_help_menu(
line_editor: Reedline,
help_completer: Box<dyn Completer>,
config: &Config,
) -> Reedline {
let mut help_menu = NuHelpMenu::default();
help_menu = match config
.help_config
.get("columns")
.and_then(|value| value.as_integer().ok())
{
Some(value) => help_menu.with_columns(value as u16),
None => help_menu,
};
help_menu = help_menu.with_column_width(
config
.help_config
.get("col_width")
.and_then(|value| value.as_integer().ok())
.map(|value| value as usize),
);
help_menu = match config
.help_config
.get("col_padding")
.and_then(|value| value.as_integer().ok())
{
Some(value) => help_menu.with_column_padding(value as usize),
None => help_menu,
};
help_menu = match config
.help_config
.get("selection_rows")
.and_then(|value| value.as_integer().ok())
{
Some(value) => help_menu.with_selection_rows(value as u16),
None => help_menu,
};
help_menu = match config
.help_config
.get("description_rows")
.and_then(|value| value.as_integer().ok())
{
Some(value) => help_menu.with_description_rows(value as usize),
None => help_menu,
};
help_menu = match config
.help_config
.get("text_style")
.and_then(|value| value.as_string().ok())
{
Some(value) => help_menu.with_text_style(lookup_ansi_color_style(&value)),
None => help_menu,
};
help_menu = match config
.help_config
.get("selected_text_style")
.and_then(|value| value.as_string().ok())
{
Some(value) => help_menu.with_selected_text_style(lookup_ansi_color_style(&value)),
None => help_menu,
};
help_menu = match config
.help_config
.get("description_text_style")
.and_then(|value| value.as_string().ok())
{
Some(value) => help_menu.with_description_text_style(lookup_ansi_color_style(&value)),
None => help_menu,
};
help_menu = match config
.help_config
.get("marker")
.and_then(|value| value.as_string().ok())
{
Some(value) => help_menu.with_marker(value),
None => help_menu,
};
line_editor.with_menu(Box::new(help_menu), Some(help_completer))
} }
fn add_menu_keybindings(keybindings: &mut Keybindings) { fn add_menu_keybindings(keybindings: &mut Keybindings) {
// Completer menu keybindings
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completer_menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
keybindings.add_binding(
KeyModifiers::SHIFT,
KeyCode::BackTab,
ReedlineEvent::MenuPrevious,
);
// History menu keybinding
keybindings.add_binding( keybindings.add_binding(
KeyModifiers::CONTROL, KeyModifiers::CONTROL,
KeyCode::Char('x'), KeyCode::Char('x'),
@ -142,19 +252,11 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
]), ]),
); );
// Help menu keybinding
keybindings.add_binding( keybindings.add_binding(
KeyModifiers::NONE, KeyModifiers::CONTROL,
KeyCode::Tab, KeyCode::Char('i'),
ReedlineEvent::UntilFound(vec![ ReedlineEvent::Menu("help_menu".to_string()),
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
keybindings.add_binding(
KeyModifiers::SHIFT,
KeyCode::BackTab,
ReedlineEvent::MenuPrevious,
); );
} }

View File

@ -1,5 +1,5 @@
use crate::reedline_config::{add_completion_menu, add_history_menu}; use crate::reedline_config::{add_completion_menu, add_help_menu, add_history_menu};
use crate::{prompt_update, reedline_config}; use crate::{prompt_update, reedline_config, NuHelpCompleter};
use crate::{ use crate::{
reedline_config::KeybindingsMode, reedline_config::KeybindingsMode,
util::{eval_source, report_error}, util::{eval_source, report_error},
@ -160,6 +160,9 @@ pub fn evaluate_repl(
line_editor = add_completion_menu(line_editor, &config); line_editor = add_completion_menu(line_editor, &config);
line_editor = add_history_menu(line_editor, &config); line_editor = add_history_menu(line_editor, &config);
let help_completer = Box::new(NuHelpCompleter::new(engine_state.clone()));
line_editor = add_help_menu(line_editor, help_completer, &config);
if is_perf_true { if is_perf_true {
info!("setup colors {}:{}:{}", file!(), line!(), column!()); info!("setup colors {}:{}:{}", file!(), line!(), column!());
} }

View File

@ -8,7 +8,7 @@ version = "0.60.1"
[dependencies] [dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.60.1" } nu-protocol = { path = "../nu-protocol", version = "0.60.1" }
nu-ansi-term = "0.45.0" nu-ansi-term = "0.45.1"
nu-json = { path = "../nu-json", version = "0.60.1" } nu-json = { path = "../nu-json", version = "0.60.1" }
nu-table = { path = "../nu-table", version = "0.60.1" } nu-table = { path = "../nu-table", version = "0.60.1" }
serde = { version="1.0.123", features=["derive"] } serde = { version="1.0.123", features=["derive"] }

View File

@ -23,7 +23,7 @@ nu-table = { path = "../nu-table", version = "0.60.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.60.1" } nu-term-grid = { path = "../nu-term-grid", version = "0.60.1" }
nu-test-support = { path = "../nu-test-support", version = "0.60.1" } nu-test-support = { path = "../nu-test-support", version = "0.60.1" }
nu-utils = { path = "../nu-utils", version = "0.60.1" } nu-utils = { path = "../nu-utils", version = "0.60.1" }
nu-ansi-term = "0.45.0" nu-ansi-term = "0.45.1"
# Potential dependencies for extras # Potential dependencies for extras
base64 = "0.13.0" base64 = "0.13.0"
@ -77,7 +77,8 @@ unicode-segmentation = "1.8.0"
url = "2.2.1" url = "2.2.1"
uuid = { version = "0.8.2", features = ["v4"] } uuid = { version = "0.8.2", features = ["v4"] }
which = { version = "4.2.2", optional = true } which = { version = "4.2.2", optional = true }
reedline = { git = "https://github.com/nushell/reedline" } #reedline = "0.3.0"
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
zip = { version="0.5.9", optional = true } zip = { version="0.5.9", optional = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]

View File

@ -33,6 +33,7 @@ pub struct Config {
pub menu_config: HashMap<String, Value>, pub menu_config: HashMap<String, Value>,
pub keybindings: Vec<ParsedKeybinding>, pub keybindings: Vec<ParsedKeybinding>,
pub history_config: HashMap<String, Value>, pub history_config: HashMap<String, Value>,
pub help_config: HashMap<String, Value>,
pub rm_always_trash: bool, pub rm_always_trash: bool,
} }
@ -55,8 +56,9 @@ impl Default for Config {
max_history_size: 1000, max_history_size: 1000,
log_level: String::new(), log_level: String::new(),
menu_config: HashMap::new(), menu_config: HashMap::new(),
keybindings: Vec::new(),
history_config: HashMap::new(), history_config: HashMap::new(),
help_config: HashMap::new(),
keybindings: Vec::new(),
rm_always_trash: false, rm_always_trash: false,
} }
} }
@ -211,13 +213,6 @@ impl Value {
eprintln!("$config.menu_config is not a record") eprintln!("$config.menu_config is not a record")
} }
} }
"keybindings" => {
if let Ok(keybindings) = create_keybindings(value, &config) {
config.keybindings = keybindings;
} else {
eprintln!("$config.keybindings is not a valid keybindings list")
}
}
"history_config" => { "history_config" => {
if let Ok(map) = create_map(value, &config) { if let Ok(map) = create_map(value, &config) {
config.history_config = map; config.history_config = map;
@ -225,6 +220,20 @@ impl Value {
eprintln!("$config.history_config is not a record") eprintln!("$config.history_config is not a record")
} }
} }
"help_config" => {
if let Ok(map) = create_map(value, &config) {
config.help_config = map;
} else {
eprintln!("$config.help_config is not a record")
}
}
"keybindings" => {
if let Ok(keybindings) = create_keybindings(value, &config) {
config.keybindings = keybindings;
} else {
eprintln!("$config.keybindings is not a valid keybindings list")
}
}
x => { x => {
eprintln!("$config.{} is an unknown config setting", x) eprintln!("$config.{} is an unknown config setting", x)
} }

View File

@ -12,7 +12,7 @@ name = "table"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
nu-ansi-term = "0.45.0" nu-ansi-term = "0.45.1"
nu-protocol = { path = "../nu-protocol", version = "0.60.1" } nu-protocol = { path = "../nu-protocol", version = "0.60.1" }
regex = "1.4" regex = "1.4"
unicode-width = "0.1.8" unicode-width = "0.1.8"

View File

@ -212,6 +212,17 @@ let $config = {
selected_text_style: green_reverse selected_text_style: green_reverse
marker: "? " marker: "? "
} }
help_config: {
columns: 4
col_width: 20 # Optional value. If missing all the screen width is used to calculate column width
col_padding: 2
selection_rows: 4
description_rows: 10
text_style: green
selected_text_style: green_reverse
description_text_style: yellow
marker: "% "
}
keybindings: [ keybindings: [
{ {
name: completion_menu name: completion_menu