diff --git a/clap_complete/src/shells/bash.rs b/clap_complete/src/shells/bash.rs index 726078ab366..cc9ce2a874e 100644 --- a/clap_complete/src/shells/bash.rs +++ b/clap_complete/src/shells/bash.rs @@ -168,29 +168,50 @@ fn option_details_for_path(cmd: &Command, path: &str) -> String { let mut opts = vec![String::new()]; for o in p.get_opts() { + let compopt = match o.get_value_hint() { + ValueHint::FilePath => Some("compopt -o filenames"), + ValueHint::DirPath => Some("compopt -o plusdirs"), + ValueHint::Other => Some("compopt -o nospace"), + _ => None, + }; + if let Some(longs) = o.get_long_and_visible_aliases() { opts.extend(longs.iter().map(|long| { - format!( - "--{}) - COMPREPLY=({}) - return 0 - ;;", - long, - vals_for(o) - ) + let mut v = vec![ + format!("--{})", long), + format!("COMPREPLY=({})", vals_for(o)), + ]; + + if let Some(copt) = compopt { + v.extend([ + r#"if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then"#.to_string(), + format!(" {}", copt), + "fi".to_string(), + ]); + } + + v.extend(["return 0", ";;"].iter().map(|s| s.to_string())); + v.join("\n ") })); } if let Some(shorts) = o.get_short_and_visible_aliases() { opts.extend(shorts.iter().map(|short| { - format!( - "-{}) - COMPREPLY=({}) - return 0 - ;;", - short, - vals_for(o) - ) + let mut v = vec![ + format!("-{})", short), + format!("COMPREPLY=({})", vals_for(o)), + ]; + + if let Some(copt) = compopt { + v.extend([ + r#"if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then"#.to_string(), + format!(" {}", copt), + "fi".to_string(), + ]); + } + + v.extend(["return 0", ";;"].iter().map(|s| s.to_string())); + v.join("\n ") })); } } @@ -210,6 +231,8 @@ fn vals_for(o: &Arg) -> String { .collect::>() .join(" ") ) + } else if o.get_value_hint() == ValueHint::DirPath { + String::from("") // should be empty to avoid duplicate candidates } else if o.get_value_hint() == ValueHint::Other { String::from("\"${cur}\"") } else { diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc index 490fcbf3c1d..2f72ccf40b2 100644 --- a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc +++ b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc @@ -542,6 +542,9 @@ _exhaustive() { ;; --other) COMPREPLY=("${cur}") + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o nospace + fi return 0 ;; --path) @@ -554,18 +557,30 @@ _exhaustive() { ;; --file) COMPREPLY=($(compgen -f "${cur}")) + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi return 0 ;; -f) COMPREPLY=($(compgen -f "${cur}")) + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi return 0 ;; --dir) - COMPREPLY=($(compgen -f "${cur}")) + COMPREPLY=() + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o plusdirs + fi return 0 ;; -d) - COMPREPLY=($(compgen -f "${cur}")) + COMPREPLY=() + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o plusdirs + fi return 0 ;; --exe) diff --git a/clap_complete/tests/snapshots/value_hint.bash b/clap_complete/tests/snapshots/value_hint.bash index 61e2f61e96e..35f6b3d56b1 100644 --- a/clap_complete/tests/snapshots/value_hint.bash +++ b/clap_complete/tests/snapshots/value_hint.bash @@ -35,6 +35,9 @@ _my-app() { ;; --other) COMPREPLY=("${cur}") + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o nospace + fi return 0 ;; --path) @@ -47,18 +50,30 @@ _my-app() { ;; --file) COMPREPLY=($(compgen -f "${cur}")) + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi return 0 ;; -f) COMPREPLY=($(compgen -f "${cur}")) + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o filenames + fi return 0 ;; --dir) - COMPREPLY=($(compgen -f "${cur}")) + COMPREPLY=() + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o plusdirs + fi return 0 ;; -d) - COMPREPLY=($(compgen -f "${cur}")) + COMPREPLY=() + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + compopt -o plusdirs + fi return 0 ;; --exe) diff --git a/clap_complete/tests/testsuite/bash.rs b/clap_complete/tests/testsuite/bash.rs index 2e960136704..7edb458cc9d 100644 --- a/clap_complete/tests/testsuite/bash.rs +++ b/clap_complete/tests/testsuite/bash.rs @@ -161,6 +161,58 @@ fn complete() { -V --generate --version quote pacman alias complete "#; let actual = runtime.complete(input, &term).unwrap(); snapbox::assert_eq(expected, actual); + + // Issue 5239 (https://github.com/clap-rs/clap/issues/5239) + let input = "exhaustive hint --file test\t"; + let expected = "exhaustive hint --file test % exhaustive hint --file tests/"; + let actual = runtime.complete(input, &term).unwrap(); + snapbox::assert_eq(expected, actual); + + { + use std::fs::File; + use std::path::Path; + + let testdir = snapbox::path::PathFixture::mutable_temp().unwrap(); + let testdir_path = testdir.path().unwrap(); + + File::create(Path::new(testdir_path).join("a_file")).unwrap(); + File::create(Path::new(testdir_path).join("b_file")).unwrap(); + std::fs::create_dir(Path::new(testdir_path).join("c_dir")).unwrap(); + std::fs::create_dir(Path::new(testdir_path).join("d_dir")).unwrap(); + + let input = format!( + "exhaustive hint --file {}/\t\t", + testdir_path.to_string_lossy() + ); + let actual = runtime.complete(input.as_str(), &term).unwrap(); + assert!( + actual.contains("a_file") + && actual.contains("b_file") + && actual.contains("c_dir") + && actual.contains("d_dir"), + "Actual output:\n{}", + actual + ); + + let input = format!( + "exhaustive hint --dir {}/\t\t", + testdir_path.to_string_lossy() + ); + let actual = runtime.complete(input.as_str(), &term).unwrap(); + assert!( + !actual.contains("a_file") + && !actual.contains("b_file") + && actual.contains("c_dir") + && actual.contains("d_dir"), + "Actual output:\n{}", + actual + ); + } + + let input = "exhaustive hint --other \t"; + let expected = "exhaustive hint --other % exhaustive hint --other "; + let actual = runtime.complete(input, &term).unwrap(); + snapbox::assert_eq(expected, actual); } #[test]