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

View File

@ -38,7 +38,7 @@ crossterm_winapi = "0.9.0"
ctrlc = "3.2.1"
log = "0.4"
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-color-config = { path = "./crates/nu-color-config", 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" }
pretty_env_logger = "0.4.0"
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"
[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-protocol = { path = "../nu-protocol", 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" }
crossterm = "0.23.0"
crossterm_winapi = "0.9.0"
miette = { version = "4.1.0", features = ["fancy"] }
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"
is_executable = "1.0.1"

View File

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

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 errors;
mod eval_file;
mod help_completions;
mod help_menu;
mod nu_highlight;
mod print;
mod prompt;
@ -18,6 +20,8 @@ pub use completions::NuCompleter;
pub use config_files::eval_config_contents;
pub use errors::CliError;
pub use eval_file::evaluate_file;
pub use help_completions::NuHelpCompleter;
pub use help_menu::NuHelpMenu;
pub use nu_highlight::NuHighlight;
pub use print::Print;
pub use prompt::NushellPrompt;

View File

@ -1,9 +1,10 @@
use super::NuHelpMenu;
use crossterm::event::{KeyCode, KeyModifiers};
use nu_color_config::lookup_ansi_color_style;
use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Value};
use reedline::{
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
@ -64,7 +65,7 @@ pub(crate) fn add_completion_menu(line_editor: Reedline, config: &Config) -> Ree
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
@ -120,10 +121,119 @@ pub(crate) fn add_history_menu(line_editor: Reedline, config: &Config) -> Reedli
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) {
// 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(
KeyModifiers::CONTROL,
KeyCode::Char('x'),
@ -142,19 +252,11 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
]),
);
// Help menu keybinding
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
keybindings.add_binding(
KeyModifiers::SHIFT,
KeyCode::BackTab,
ReedlineEvent::MenuPrevious,
KeyModifiers::CONTROL,
KeyCode::Char('i'),
ReedlineEvent::Menu("help_menu".to_string()),
);
}

View File

@ -1,5 +1,5 @@
use crate::reedline_config::{add_completion_menu, add_history_menu};
use crate::{prompt_update, reedline_config};
use crate::reedline_config::{add_completion_menu, add_help_menu, add_history_menu};
use crate::{prompt_update, reedline_config, NuHelpCompleter};
use crate::{
reedline_config::KeybindingsMode,
util::{eval_source, report_error},
@ -160,6 +160,9 @@ pub fn evaluate_repl(
line_editor = add_completion_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 {
info!("setup colors {}:{}:{}", file!(), line!(), column!());
}

View File

@ -8,7 +8,7 @@ version = "0.60.1"
[dependencies]
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-table = { path = "../nu-table", version = "0.60.1" }
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-test-support = { path = "../nu-test-support", 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
base64 = "0.13.0"
@ -77,7 +77,8 @@ unicode-segmentation = "1.8.0"
url = "2.2.1"
uuid = { version = "0.8.2", features = ["v4"] }
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 }
[target.'cfg(unix)'.dependencies]

View File

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

View File

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

View File

@ -212,6 +212,17 @@ let $config = {
selected_text_style: green_reverse
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: [
{
name: completion_menu