From ef898fe43cb5d045e079403b67a76bc28def29b0 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 12 Dec 2023 17:12:15 +0100 Subject: [PATCH 1/3] basic nushell completions --- complete/src/lib.rs | 2 ++ complete/src/nu.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 complete/src/nu.rs diff --git a/complete/src/lib.rs b/complete/src/lib.rs index 15dc7ba..5d8649c 100644 --- a/complete/src/lib.rs +++ b/complete/src/lib.rs @@ -16,6 +16,7 @@ mod fish; mod man; mod md; +mod nu; mod zsh; /// A description of a CLI command @@ -72,6 +73,7 @@ pub fn render(c: &Command, shell: &str) -> String { "md" => md::render(c), "fish" => fish::render(c), "zsh" => zsh::render(c), + "nu" | "nushell" => nu::render(c), "man" => man::render(c), "sh" | "bash" | "csh" | "elvish" | "powershell" => panic!("shell '{shell}' completion is not implemented yet!"), _ => panic!("unknown option '{shell}'! Expected one of: \"md\", \"fish\", \"zsh\", \"man\", \"sh\", \"bash\", \"csh\", \"elvish\", \"powershell\""), diff --git a/complete/src/nu.rs b/complete/src/nu.rs new file mode 100644 index 0000000..95310aa --- /dev/null +++ b/complete/src/nu.rs @@ -0,0 +1,51 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use crate::{Arg, Command, Flag, Value}; +use std::fmt::Write; + +/// Create completion script for `nushell` +pub fn render(c: &Command) -> String { + let mut args = Vec::new(); + let indent = " ".repeat(4); + + for Arg { + short, + long, + help, + value: _value, + } in &c.args + { + for Flag { flag, value } in short { + let value = if let Value::Required(_) | Value::Optional(_) = value { + ": string" + } else { + "" + }; + args.push((format!("-{flag}{value}"), help)); + } + for Flag { flag, value } in long { + let value = if let Value::Required(_) | Value::Optional(_) = value { + ": string" + } else { + "" + }; + args.push((format!("--{flag}{value}"), help)); + } + } + let longest_arg = args.iter().map(|a| a.0.len()).max().unwrap_or_default(); + let mut arg_str = String::new(); + for (a, h) in args { + writeln!(arg_str, "{indent}{a: String { + format!( + "\ + export extern {name} [\n{args}\ + ]\n\ + " + ) +} From 27293fb53edafe1185cb1e7b97871ed2c0aafc14 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 13 Dec 2023 12:17:41 +0100 Subject: [PATCH 2/3] add completion example --- examples/completion.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/completion.rs diff --git a/examples/completion.rs b/examples/completion.rs new file mode 100644 index 0000000..22679c6 --- /dev/null +++ b/examples/completion.rs @@ -0,0 +1,42 @@ +use std::path::PathBuf; + +use uutils_args::{Arguments, Options, Value}; + +#[derive(Value)] +enum Number { + #[value] + One, + #[value] + Two, + #[value] + Three, +} + +#[derive(Arguments)] +enum Arg { + /// Give it nothing! + #[arg("-f", "--flag")] + Flag, + + // Completion is derived from the `Number` type, through the `Value` trait + /// Give it a number! + #[arg("-n N", "--number=N")] + Number(Number), + + // Completion is derived from the `PathBuf` type + /// Give it a path! + #[arg("-p P", "--path=P")] + Path(PathBuf), +} + +struct Settings; + +impl Options for Settings { + fn apply(&mut self, _arg: Arg) { + panic!("Compile with the 'parse-is-complete' feature!") + } +} + +fn main() { + Settings.parse(std::env::args_os()); +} From ff6933e863e9854b412f523cfaeb67fab5914d91 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 13 Dec 2023 12:17:56 +0100 Subject: [PATCH 3/3] nu completion: support value options --- complete/src/nu.rs | 82 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/complete/src/nu.rs b/complete/src/nu.rs index 95310aa..fb9016c 100644 --- a/complete/src/nu.rs +++ b/complete/src/nu.rs @@ -1,36 +1,39 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::{Arg, Command, Flag, Value}; +use crate::{Arg, Command, Flag, Value, ValueHint}; use std::fmt::Write; /// Create completion script for `nushell` pub fn render(c: &Command) -> String { let mut args = Vec::new(); + let command_name = c.name; + let mut complete_commands = Vec::new(); let indent = " ".repeat(4); - for Arg { - short, - long, - help, - value: _value, - } in &c.args - { - for Flag { flag, value } in short { + for arg in &c.args { + let hint = if let Some((cmd, hint_name)) = render_completion_command(command_name, arg) { + complete_commands.push(cmd); + hint_name + } else { + "".into() + }; + + for Flag { flag, value } in &arg.short { let value = if let Value::Required(_) | Value::Optional(_) = value { - ": string" + format!(": string{hint}") } else { - "" + "".into() }; - args.push((format!("-{flag}{value}"), help)); + args.push((format!("-{flag}{value}"), arg.help)); } - for Flag { flag, value } in long { + for Flag { flag, value } in &arg.long { let value = if let Value::Required(_) | Value::Optional(_) = value { - ": string" + format!(": string{hint}") } else { - "" + "".into() }; - args.push((format!("--{flag}{value}"), help)); + args.push((format!("--{flag}{value}"), arg.help)); } } let longest_arg = args.iter().map(|a| a.0.len()).max().unwrap_or_default(); @@ -38,14 +41,45 @@ pub fn render(c: &Command) -> String { for (a, h) in args { writeln!(arg_str, "{indent}{a: Option<(String, String)> { + let val = arg.value.as_ref()?; + + // It could be that there is only a `dd` style argument. In that case, nu won't support it; + let arg_name = arg.long.first().or(arg.short.first())?.flag; + + render_value_hint(val).map(|hint| { + let name = format!("nu-complete {command_name} {arg_name}"); + let cmd = format!("def \"{name}\" [] {{\n {hint}\n}}"); + let hint_str = format!("@\"{name}\""); + (cmd, hint_str) + }) +} + +fn render_value_hint(value: &ValueHint) -> Option { + match value { + ValueHint::Strings(s) => { + let vals = s + .iter() + .map(|s| format!("\"{s}\"")) + .collect::>() + .join(", "); + Some(format!("[{vals}]")) + } + // The path arguments could be improved, but nu currently does not give + // us enough context to improve the default completions. + ValueHint::Unknown + | ValueHint::AnyPath + | ValueHint::FilePath + | ValueHint::ExecutablePath + | ValueHint::DirPath + | ValueHint::Username + | ValueHint::Hostname => None, + } } -fn template(name: &str, args: &str) -> String { - format!( - "\ - export extern {name} [\n{args}\ - ]\n\ - " - ) +fn template(name: &str, complete_commands: &str, args: &str) -> String { + format!("{complete_commands}\n\nexport extern \"{name}\" [\n{args}]\n") }