From af4d89c95bcc0b57794b728feeeb612adbc4d468 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 30 Mar 2024 21:07:11 +0100 Subject: [PATCH 1/3] fix documented list of supported shell completions --- complete/src/lib.rs | 2 +- docs/guide/completions.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/complete/src/lib.rs b/complete/src/lib.rs index 5d8649c..ee84b8e 100644 --- a/complete/src/lib.rs +++ b/complete/src/lib.rs @@ -76,6 +76,6 @@ pub fn render(c: &Command, shell: &str) -> String { "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\""), + _ => panic!("unknown option '{shell}'! Expected one of: \"md\", \"fish\", \"zsh\", \"nu[shell]\", \"man\", \"sh\", \"bash\", \"csh\", \"elvish\", \"powershell\""), } } diff --git a/docs/guide/completions.md b/docs/guide/completions.md index f325b91..d89d3dd 100644 --- a/docs/guide/completions.md +++ b/docs/guide/completions.md @@ -33,7 +33,7 @@ Shell completions and documentation can be generated automatically by this crate cargo run --features parse-is-complete -- [shell] ``` -The `[shell]` value here can be `fish`, `zsh`, `bash`, `powershell`, `elvish` or `nu`. +The `[shell]` value here can be `fish`, `zsh`, `nu`, `sh`, `bash`, `csh`, `elvish`, or `powershell`. > **Note**: Some of these remain unimplemented as of writing. @@ -47,4 +47,4 @@ If you do not want to hijack the [`Options::parse`](crate::Options::parse) funct [Up](super) [Next]() - \ No newline at end of file + From 835f9dceb6ebe0d3cad47d0b2bdd4b38b1b3bab7 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sat, 30 Mar 2024 22:37:04 +0100 Subject: [PATCH 2/3] implement rudimentary bash completion --- complete/src/bash.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++ complete/src/lib.rs | 4 ++- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 complete/src/bash.rs diff --git a/complete/src/bash.rs b/complete/src/bash.rs new file mode 100644 index 0000000..37b7711 --- /dev/null +++ b/complete/src/bash.rs @@ -0,0 +1,64 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use crate::{Command, Flag}; + +/// Create completion script for `bash` +/// +/// Short and long options are combined into single `complete` calls, even if +/// they differ in whether they take arguments or not; just like in case of `fish`. +/// Also, pretend that files are fine in any position. ValueHints are ignored entirely. +pub fn render(c: &Command) -> String { + let mut out = String::new(); + let name = &c.name; + // Register _comp_uu_FOO as a bash function that computes completions: + out.push_str(&format!("complete -F _comp_uu_{name} {name};")); + out.push_str(&format!("_comp_uu_{name}()")); + // Unless the current argument starts with "-", pre-populate the completions list with all files and dirs: + out.push_str("{ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \""); + for arg in &c.args { + for Flag { flag, .. } in &arg.short { + out.push_str(&format!("-{flag} ")); + } + for Flag { flag, .. } in &arg.long { + out.push_str(&format!("--{flag} ")); + } + } + out.push_str("\" -- \"$cur\"));}\n"); + out +} + +#[cfg(test)] +mod test { + use super::render; + use crate::{Arg, Command, Flag, Value}; + + #[test] + fn simple() { + let c = Command { + name: "foo", + args: vec![ + Arg { + short: vec![Flag { + flag: "a", + value: Value::No, + }], + long: vec![Flag { + flag: "all", + value: Value::No, + }], + ..Arg::default() + }, + Arg { + short: vec![Flag { + flag: "x", + value: Value::No, + }], + ..Arg::default() + }, + ], + ..Command::default() + }; + assert_eq!(render(&c), "complete -F _comp_uu_foo foo;_comp_uu_foo(){ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \"-a --all -x \" -- \"$cur\"));}\n") + } +} diff --git a/complete/src/lib.rs b/complete/src/lib.rs index ee84b8e..39cbd62 100644 --- a/complete/src/lib.rs +++ b/complete/src/lib.rs @@ -13,6 +13,7 @@ //! - Some information is removed because it is irrelevant for completion and documentation //! - This struct is meant to exist at runtime of the program //! +mod bash; mod fish; mod man; mod md; @@ -75,7 +76,8 @@ pub fn render(c: &Command, shell: &str) -> String { "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!"), + "bash" => bash::render(c), + "sh" | "csh" | "elvish" | "powershell" => panic!("shell '{shell}' completion is not implemented yet!"), _ => panic!("unknown option '{shell}'! Expected one of: \"md\", \"fish\", \"zsh\", \"nu[shell]\", \"man\", \"sh\", \"bash\", \"csh\", \"elvish\", \"powershell\""), } } From f92e9575753c87a8b117a90d02a1404cfdd8bfba Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Sun, 31 Mar 2024 15:21:19 +0200 Subject: [PATCH 3/3] handle special-case program name '[' --- complete/src/bash.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/complete/src/bash.rs b/complete/src/bash.rs index 37b7711..265c2c0 100644 --- a/complete/src/bash.rs +++ b/complete/src/bash.rs @@ -10,10 +10,14 @@ use crate::{Command, Flag}; /// Also, pretend that files are fine in any position. ValueHints are ignored entirely. pub fn render(c: &Command) -> String { let mut out = String::new(); - let name = &c.name; + // Be careful around the program '['! + let name_identifier = if c.name == "[" { &"bracket" } else { &c.name }; // Register _comp_uu_FOO as a bash function that computes completions: - out.push_str(&format!("complete -F _comp_uu_{name} {name};")); - out.push_str(&format!("_comp_uu_{name}()")); + out.push_str(&format!( + "complete -F _comp_uu_{name_identifier} '{}';", + &c.name + )); + out.push_str(&format!("_comp_uu_{name_identifier}()")); // Unless the current argument starts with "-", pre-populate the completions list with all files and dirs: out.push_str("{ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \""); for arg in &c.args { @@ -59,6 +63,22 @@ mod test { ], ..Command::default() }; - assert_eq!(render(&c), "complete -F _comp_uu_foo foo;_comp_uu_foo(){ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \"-a --all -x \" -- \"$cur\"));}\n") + assert_eq!(render(&c), "complete -F _comp_uu_foo 'foo';_comp_uu_foo(){ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \"-a --all -x \" -- \"$cur\"));}\n") + } + + #[test] + fn bracket() { + let c = Command { + name: "[", + args: vec![Arg { + short: vec![Flag { + flag: "x", + value: Value::No, + }], + ..Arg::default() + }], + ..Command::default() + }; + assert_eq!(render(&c), "complete -F _comp_uu_bracket '[';_comp_uu_bracket(){ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \"-x \" -- \"$cur\"));}\n") } }