From fe16f24ed89be57ac75814c3c294c805a0e4df12 Mon Sep 17 00:00:00 2001 From: sudotac Date: Wed, 31 Jan 2024 23:34:57 +0900 Subject: [PATCH 1/2] test(complete): Verify filename splitting --- clap_complete/tests/testsuite/bash.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/clap_complete/tests/testsuite/bash.rs b/clap_complete/tests/testsuite/bash.rs index 1392294bd2d..d64d155a521 100644 --- a/clap_complete/tests/testsuite/bash.rs +++ b/clap_complete/tests/testsuite/bash.rs @@ -222,6 +222,27 @@ fn complete() { ); } + // Issue 5313 (https://github.com/clap-rs/clap/issues/5313) + { + 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("foo bar.txt")).unwrap(); + File::create(Path::new(testdir_path).join("baz\tqux.txt")).unwrap(); + + let input = format!( + "exhaustive hint --file {}/\t\t", + testdir_path.to_string_lossy() + ); + let expected = r#"% +foo bar.txt baz qux.txt "#; + let actual = runtime.complete(input.as_str(), &term).unwrap(); + snapbox::assert_eq(expected, actual); + } + let input = "exhaustive hint --other \t"; let expected = "exhaustive hint --other % exhaustive hint --other "; let actual = runtime.complete(input, &term).unwrap(); From 1edffb857618261d1b60ee156107e3c1d1da1a7f Mon Sep 17 00:00:00 2001 From: sudotac Date: Wed, 31 Jan 2024 23:37:19 +0900 Subject: [PATCH 2/2] fix(complete): Prevent filenames splitting Fix #5313 --- clap_complete/src/shells/bash.rs | 42 +++++++++++++++---- .../home/static/exhaustive/bash/.bashrc | 16 +++++++ clap_complete/tests/snapshots/value_hint.bash | 16 +++++++ clap_complete/tests/testsuite/bash.rs | 2 +- 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/clap_complete/src/shells/bash.rs b/clap_complete/src/shells/bash.rs index f390853a159..5fb39f4b965 100644 --- a/clap_complete/src/shells/bash.rs +++ b/clap_complete/src/shells/bash.rs @@ -178,10 +178,23 @@ fn option_details_for_path(cmd: &Command, path: &str) -> String { if let Some(longs) = o.get_long_and_visible_aliases() { opts.extend(longs.iter().map(|long| { - let mut v = vec![ - format!("--{})", long), - format!("COMPREPLY=({})", vals_for(o)), - ]; + let mut v = vec![format!("--{})", long)]; + + if o.get_value_hint() == ValueHint::FilePath { + v.extend([ + "local oldifs".to_string(), + "if [[ -v IFS ]]; then".to_string(), + r#" oldifs="$IFS""#.to_string(), + "fi".to_string(), + r#"IFS=$'\n'"#.to_string(), + format!("COMPREPLY=({})", vals_for(o)), + "if [[ -v oldifs ]]; then".to_string(), + r#" IFS="$oldifs""#.to_string(), + "fi".to_string(), + ]); + } else { + v.push(format!("COMPREPLY=({})", vals_for(o))); + } if let Some(copt) = compopt { v.extend([ @@ -198,10 +211,23 @@ fn option_details_for_path(cmd: &Command, path: &str) -> String { if let Some(shorts) = o.get_short_and_visible_aliases() { opts.extend(shorts.iter().map(|short| { - let mut v = vec![ - format!("-{})", short), - format!("COMPREPLY=({})", vals_for(o)), - ]; + let mut v = vec![format!("-{})", short)]; + + if o.get_value_hint() == ValueHint::FilePath { + v.extend([ + "local oldifs".to_string(), + "if [[ -v IFS ]]; then".to_string(), + r#" oldifs="$IFS""#.to_string(), + "fi".to_string(), + r#"IFS=$'\n'"#.to_string(), + format!("COMPREPLY=({})", vals_for(o)), + "if [[ -v oldifs ]]; then".to_string(), + r#" IFS="$oldifs""#.to_string(), + "fi".to_string(), + ]); + } else { + v.push(format!("COMPREPLY=({})", vals_for(o))); + } if let Some(copt) = compopt { v.extend([ diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc index 2f72ccf40b2..a004f9148fe 100644 --- a/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc +++ b/clap_complete/tests/snapshots/home/static/exhaustive/bash/.bashrc @@ -556,14 +556,30 @@ _exhaustive() { return 0 ;; --file) + local oldifs + if [[ -v IFS ]]; then + oldifs="$IFS" + fi + IFS=$'\n' COMPREPLY=($(compgen -f "${cur}")) + if [[ -v oldifs ]]; then + IFS="$oldifs" + fi if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then compopt -o filenames fi return 0 ;; -f) + local oldifs + if [[ -v IFS ]]; then + oldifs="$IFS" + fi + IFS=$'\n' COMPREPLY=($(compgen -f "${cur}")) + if [[ -v oldifs ]]; then + IFS="$oldifs" + fi if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then compopt -o filenames fi diff --git a/clap_complete/tests/snapshots/value_hint.bash b/clap_complete/tests/snapshots/value_hint.bash index 35f6b3d56b1..e2a7f9efd37 100644 --- a/clap_complete/tests/snapshots/value_hint.bash +++ b/clap_complete/tests/snapshots/value_hint.bash @@ -49,14 +49,30 @@ _my-app() { return 0 ;; --file) + local oldifs + if [[ -v IFS ]]; then + oldifs="$IFS" + fi + IFS=$'\n' COMPREPLY=($(compgen -f "${cur}")) + if [[ -v oldifs ]]; then + IFS="$oldifs" + fi if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then compopt -o filenames fi return 0 ;; -f) + local oldifs + if [[ -v IFS ]]; then + oldifs="$IFS" + fi + IFS=$'\n' COMPREPLY=($(compgen -f "${cur}")) + if [[ -v oldifs ]]; then + IFS="$oldifs" + fi if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then compopt -o filenames fi diff --git a/clap_complete/tests/testsuite/bash.rs b/clap_complete/tests/testsuite/bash.rs index d64d155a521..937f112cfcd 100644 --- a/clap_complete/tests/testsuite/bash.rs +++ b/clap_complete/tests/testsuite/bash.rs @@ -238,7 +238,7 @@ fn complete() { testdir_path.to_string_lossy() ); let expected = r#"% -foo bar.txt baz qux.txt "#; +foo bar.txt baz^Iqux.txt "#; let actual = runtime.complete(input.as_str(), &term).unwrap(); snapbox::assert_eq(expected, actual); }