From c4d5e2703ae61fc2caceb97a35b71ccddf44956d Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Fri, 13 Feb 2026 00:54:06 +0000 Subject: [PATCH 1/2] test(complete): add test for Bash COMP_WORDBREAKS handling The Bash registration script does not currently handle COMP_WORDBREAKS, which causes double-prefixing when completing values containing = or :. --- clap_complete/src/env/shells.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/clap_complete/src/env/shells.rs b/clap_complete/src/env/shells.rs index a46d5d439ee..59ab710209a 100644 --- a/clap_complete/src/env/shells.rs +++ b/clap_complete/src/env/shells.rs @@ -463,7 +463,25 @@ impl Zsh { #[cfg(test)] mod tests { use super::*; - use snapbox::assert_data_eq; + + #[test] + #[cfg(feature = "unstable-dynamic")] + fn bash_registration_handles_wordbreaks() { + let mut buf = Vec::new(); + let bash = Bash; + bash.write_registration("COMPLETE", "my-app", "my-app", "my-app", &mut buf) + .expect("write_registration failed"); + let script = String::from_utf8(buf).expect("Invalid UTF-8"); + + // The registration script must check for COMP_WORDBREAKS to handle shells + // where `=` and `:` are word-break characters. Without this, completing + // `--flag=value` would produce `--flag=--flag=value` because Bash splits + // at `=` and only replaces the part after it. + assert!( + !script.contains("COMP_WORDBREAKS"), + "registration script does not yet reference COMP_WORDBREAKS" + ); + } // This test verifies that fish shell path quoting works with or without spaces in the path. #[test] From df24ba0b7b57c0b467a2b574429e68328b99da3d Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Fri, 13 Feb 2026 00:55:01 +0000 Subject: [PATCH 2/2] fix(complete): handle COMP_WORDBREAKS in Bash completion Add prefix-stripping logic to the Bash registration script to prevent double-prefixing when COMP_WORDBREAKS contains = or :. --- clap_complete/src/env/shells.rs | 22 +++++++++++++++++-- .../home/dynamic-env/exhaustive/bash/.bashrc | 9 ++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/clap_complete/src/env/shells.rs b/clap_complete/src/env/shells.rs index 59ab710209a..bcb93d2d35d 100644 --- a/clap_complete/src/env/shells.rs +++ b/clap_complete/src/env/shells.rs @@ -54,6 +54,15 @@ _clap_complete_NAME() { elif [[ $_CLAP_COMPLETE_SPACE == false ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then compopt -o nospace fi + if [[ -n ${COMP_WORDBREAKS+x} ]]; then + # If the current word contains a word break character, we need to strip + # the prefix up to that character from each completion + local prefix + prefix="${2%"${2##*[${COMP_WORDBREAKS}]}"}" + if [[ -n "$prefix" ]]; then + COMPREPLY=("${COMPREPLY[@]#"$prefix"}") + fi + fi } if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then complete -o nospace -o bashdefault -o nosort -F _clap_complete_NAME BIN @@ -478,8 +487,16 @@ mod tests { // `--flag=value` would produce `--flag=--flag=value` because Bash splits // at `=` and only replaces the part after it. assert!( - !script.contains("COMP_WORDBREAKS"), - "registration script does not yet reference COMP_WORDBREAKS" + script.contains("COMP_WORDBREAKS"), + "registration script must reference COMP_WORDBREAKS to handle word-break characters" + ); + + // The script must strip the prefix up to the word-break character from + // each completion entry using the `${COMPREPLY[@]#"$prefix"}` pattern. + // This is the shell-side fix that prevents double-prefixing. + assert!( + script.contains(r#"COMPREPLY=("${COMPREPLY[@]#"$prefix"}")"#), + "registration script must strip word-break prefix from COMPREPLY entries" ); } @@ -488,6 +505,7 @@ mod tests { #[cfg(all(unix, feature = "unstable-dynamic"))] #[cfg(feature = "unstable-shell-tests")] fn fish_env_completer_path_quoting_works() { + use snapbox::assert_data_eq; // Returns the dynamic registration line for the fish shell, for example: // complete --keep-order --exclusive --command my-bin --arguments "(COMPLETE=fish /path/to/my-bin ... )" let get_fish_registration = |completer_bin: &str| { diff --git a/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/bash/.bashrc index 7ecc315c6ac..c658f2da87b 100644 --- a/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/bash/.bashrc +++ b/clap_complete/tests/snapshots/home/dynamic-env/exhaustive/bash/.bashrc @@ -27,6 +27,15 @@ _clap_complete_exhaustive() { elif [[ $_CLAP_COMPLETE_SPACE == false ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then compopt -o nospace fi + if [[ -n ${COMP_WORDBREAKS+x} ]]; then + # If the current word contains a word break character, we need to strip + # the prefix up to that character from each completion + local prefix + prefix="${2%"${2##*[${COMP_WORDBREAKS}]}"}" + if [[ -n "$prefix" ]]; then + COMPREPLY=("${COMPREPLY[@]#"$prefix"}") + fi + fi } if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then complete -o nospace -o bashdefault -o nosort -F _clap_complete_exhaustive exhaustive