Repository: clap-rs/clap
Pull request #5602 (2024-07-26 13:27:06)

Support delimited values in native completions
<!--
Thanks for helping out!

Please link the appropriate issue from your PR.

If you don't have an issue, we'd recommend starting with one first so the PR can focus on the
implementation (unless its an obvious bug or documentation fix that will have
little conversation).
-->
**Related issue** https://github.com/clap-rs/clap/issues/3922

- [x] Support `--flag bar1,bar1,[TAB]`
- [x] Support `--flag=bar1,bar2,[TAB]`
- [x] Support `-F bar1,bar2,[TAB]`
- [x] Support `-F=bar1,bar2,[TAB]`
- [x] Support delimited values for positional argument in native completions. `-- a_pos,[TAB]`


epage: >  Should we support delimited values for positional argument in native completions.

Yes, a value is a value

Patch:
diff --git a/clap_complete/src/dynamic/complete.rs b/clap_complete/src/dynamic/complete.rs
index dc233931..4f1fea45 100644
--- a/clap_complete/src/dynamic/complete.rs
+++ b/clap_complete/src/dynamic/complete.rs
@@ -262,6 +262,9 @@ fn complete_arg_value(
     let mut values = Vec::new();
     debug!("complete_arg_value: arg={arg:?}, value={value:?}");
 
+    let (prefix, value) =
+        rsplit_delimiter(value, arg.get_value_delimiter()).unwrap_or((None, value));
+
     let value_os = match value {
         Ok(value) => OsStr::new(value),
         Err(value_os) => value_os,
@@ -316,9 +319,28 @@ fn complete_arg_value(
         values.sort();
     }
 
+    if let Some(prefix) = prefix {
+        values = values
+            .into_iter()
+            .map(|comp| comp.add_prefix(prefix))
+            .collect();
+    }
     values
 }
 
+fn rsplit_delimiter<'s, 'o>(
+    value: Result<&'s str, &'o OsStr>,
+    delimiter: Option<char>,
+) -> Option<(Option<&'s str>, Result<&'s str, &'o OsStr>)> {
+    let delimiter = delimiter?;
+    let value = value.ok()?;
+    let pos = value.rfind(delimiter)?;
+    let (prefix, value) = value
+        .split_at_checked(pos + delimiter.len_utf8())
+        .expect("since delimiter was found, it is within bounds");
+    Some((Some(prefix), Ok(value)))
+}
+
 fn complete_path(
     value_os: &OsStr,
     current_dir: Option<&std::path::Path>,


Test patch:
diff --git a/clap_complete/tests/testsuite/dynamic.rs b/clap_complete/tests/testsuite/dynamic.rs
index 6d8138f1..bbde8168 100644
--- a/clap_complete/tests/testsuite/dynamic.rs
+++ b/clap_complete/tests/testsuite/dynamic.rs
@@ -615,6 +615,164 @@ pos_c
     );
 }
 
+#[test]
+fn suggest_delimiter_values() {
+    let mut cmd = Command::new("delimiter")
+        .arg(
+            clap::Arg::new("delimiter")
+                .long("delimiter")
+                .short('D')
+                .value_parser([
+                    PossibleValue::new("comma"),
+                    PossibleValue::new("space"),
+                    PossibleValue::new("tab"),
+                ])
+                .value_delimiter(','),
+        )
+        .arg(
+            clap::Arg::new("pos")
+                .index(1)
+                .value_parser(["a_pos", "b_pos", "c_pos"])
+                .value_delimiter(','),
+        );
+
+    assert_data_eq!(
+        complete!(cmd, "--delimiter [TAB]"),
+        snapbox::str![
+            "comma
+space
+tab"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--delimiter=[TAB]"),
+        snapbox::str![
+            "--delimiter=comma
+--delimiter=space
+--delimiter=tab"
+        ]
+    );
+
+    assert_data_eq!(complete!(cmd, "--delimiter c[TAB]"), snapbox::str!["comma"]);
+
+    assert_data_eq!(
+        complete!(cmd, "--delimiter=c[TAB]"),
+        snapbox::str!["--delimiter=comma"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--delimiter comma,[TAB]"),
+        snapbox::str![
+            "comma,comma
+comma,space
+comma,tab"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--delimiter=comma,[TAB]"),
+        snapbox::str![
+            "--delimiter=comma,comma
+--delimiter=comma,space
+--delimiter=comma,tab
+--delimiter=comma,a_pos
+--delimiter=comma,b_pos
+--delimiter=comma,c_pos"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--delimiter comma,s[TAB]"),
+        snapbox::str!["comma,space"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "--delimiter=comma,s[TAB]"),
+        snapbox::str!["--delimiter=comma,space"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-D [TAB]"),
+        snapbox::str![
+            "comma
+space
+tab"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-D=[TAB]"),
+        snapbox::str![
+            "-D=comma
+-D=space
+-D=tab"
+        ]
+    );
+
+    assert_data_eq!(complete!(cmd, "-D c[TAB]"), snapbox::str!["comma"]);
+
+    assert_data_eq!(complete!(cmd, "-D=c[TAB]"), snapbox::str!["-D=comma"]);
+
+    assert_data_eq!(
+        complete!(cmd, "-D comma,[TAB]"),
+        snapbox::str![
+            "comma,comma
+comma,space
+comma,tab"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-D=comma,[TAB]"),
+        snapbox::str![
+            "-D=comma,comma
+-D=comma,space
+-D=comma,tab
+-D=comma,a_pos
+-D=comma,b_pos
+-D=comma,c_pos"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-D comma,s[TAB]"),
+        snapbox::str!["comma,space"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-D=comma,s[TAB]"),
+        snapbox::str!["-D=comma,space"]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-- [TAB]"),
+        snapbox::str![
+            "--delimiter
+--help\tPrint help
+-D
+-h\tPrint help
+a_pos
+b_pos
+c_pos"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, " -- a_pos,[TAB]"),
+        snapbox::str![
+            "a_pos,a_pos
+a_pos,b_pos
+a_pos,c_pos"
+        ]
+    );
+
+    assert_data_eq!(
+        complete!(cmd, "-- a_pos,b[TAB]"),
+        snapbox::str!["a_pos,b_pos"]
+    );
+}
+
 fn complete(cmd: &mut Command, args: impl AsRef<str>, current_dir: Option<&Path>) -> String {
     let input = args.as_ref();
     let mut args = vec![std::ffi::OsString::from(cmd.get_name())];