Repository: pacak/bpaf
Pull request #384 (2024-08-16 13:42:00)

Autocomplete improvements
- don't demand for disambiguation during completion process 
- additional tests


Patch:
diff --git a/src/info.rs b/src/info.rs
index a90922e..da190f6 100644
--- a/src/info.rs
+++ b/src/info.rs
@@ -212,7 +212,15 @@ impl<T> OptionParser<T> {
 
         // this only handles disambiguation failure in construct
         if let Some(msg) = err {
-            return Err(msg.render(&state, &self.inner.meta()));
+            #[cfg(feature = "autocomplete")]
+            let check_disambiguation = state.comp_ref().is_none();
+
+            #[cfg(not(feature = "autocomplete"))]
+            let check_disambiguation = false;
+
+            if check_disambiguation {
+                return Err(msg.render(&state, &self.inner.meta()));
+            }
         }
 
         self.run_subparser(&mut state)


Test patch:
diff --git a/tests/completion.rs b/tests/completion.rs
index d4caf76..8a16904 100644
--- a/tests/completion.rs
+++ b/tests/completion.rs
@@ -612,6 +612,15 @@ fn test_completer(input: &String) -> Vec<(&'static str, Option<&'static str>)> {
     vec
 }
 
+fn test_completer2(input: &String) -> Vec<(&'static str, Option<&'static str>)> {
+    let items = ["auto", "mala"];
+    items
+        .iter()
+        .filter(|item| item.starts_with(input))
+        .map(|item| (*item, None))
+        .collect::<Vec<_>>()
+}
+
 fn test_completer_descr(input: &String) -> Vec<(&'static str, Option<&'static str>)> {
     let items = ["alpha", "beta", "banana", "cat", "durian"];
     items
@@ -1062,16 +1071,63 @@ fn suggest_double_dash_automatically_for_strictly_positional() {
 }
 
 #[test]
-#[should_panic(expected = "app supports ")]
+fn multiple_adjacent_single_letter_vals() {
+    // but not really - test is incomplete
+    let a = short('a').switch();
+    let b = short('b').switch();
+    let c = short('c').switch();
+    let parser = construct!(a, b, c).to_options();
+
+    // with no input the right behavior is to suggest all the switches
+    let r = parser
+        .run_inner(Args::from(&[""]).set_comp(0))
+        .unwrap_err()
+        .unwrap_stdout();
+    assert_eq!(r, "-a\t-a\t\t\n-b\t-b\t\t\n-c\t-c\t\t\n\n");
+
+    // with a single item present separately we should suggest the remaining two
+    let r = parser
+        .run_inner(Args::from(&["-a", ""]).set_comp(0))
+        .unwrap_err()
+        .unwrap_stdout();
+    assert_eq!(r, "-b\t-b\t\t\n-c\t-c\t\t\n\n");
+
+    // trying to complete a pair of flags should dump current state
+    let r = parser
+        .run_inner(Args::from(&["-ab"]).set_comp(0))
+        .unwrap_err()
+        .unwrap_stdout();
+    assert_eq!(r, "-ab\n");
+
+    // with a single valid item we should suggest it
+    let r = parser
+        .run_inner(Args::from(&["-ab", ""]).set_comp(0))
+        .unwrap_err()
+        .unwrap_stdout();
+    assert_eq!(r, "-c");
+
+    // should I change the representation for the completions? Knowing that somethnig is a
+    // flag with a short or long name is no longer necessary
+    // now I need to be able to represent adding a -v to -f to get -fv, so pretty printed value is
+    // -v, substitution is `-fv`...
+    //
+    // FWIW bash doesn't complete short flags in combination... should we behave the same way?
+    //
+    // This whole test is incomplete. We are not actually testing or even supporting
+}
+
+#[test]
 fn ambiguity_no_resolve() {
     let a0 = short('a').switch().count();
     let a1 = short('a').argument::<usize>("AAAAAA");
     let parser = construct!([a0, a1]).to_options();
 
-    parser
+    let r = parser
         .run_inner(Args::from(&["-aaa"]).set_comp(0))
         .unwrap_err()
         .unwrap_stdout();
+
+    assert_eq!(r, "-aaa\n");
 }
 
 #[test]
@@ -1287,6 +1343,26 @@ sample\tsample\t\t\n\n"
     );
 }
 
+#[test]
+fn pair_of_positionals() {
+    // with positional items only current item should make suggestions, not both...
+    let a = positional::<String>("A").complete(test_completer);
+    let b = positional::<String>("B").complete(test_completer2);
+    let parser = construct!(a, b).to_options();
+
+    let r = parser
+        .run_inner(Args::from(&["a"]).set_comp(0))
+        .unwrap_err()
+        .unwrap_stdout();
+    assert_eq!(r, "alpha");
+
+    let r = parser
+        .run_inner(Args::from(&["alpha", "a"]).set_comp(0))
+        .unwrap_err()
+        .unwrap_stdout();
+    assert_eq!(r, "auto");
+}
+
 #[test]
 fn double_dash_as_positional() {
     let parser = positional::<String>("P")
@@ -1294,6 +1370,12 @@ fn double_dash_as_positional() {
         .complete(test_completer)
         .to_options();
 
+    let r = parser
+        .run_inner(Args::from(&["--", "a"]).set_comp(0))
+        .unwrap_err()
+        .unwrap_stdout();
+    assert_eq!(r, "alpha");
+
     let r = parser
         .run_inner(Args::from(&["a"]).set_comp(0))
         .unwrap_err()
@@ -1312,12 +1394,6 @@ fn double_dash_as_positional() {
         .unwrap_stdout();
     assert_eq!(r, "--\n");
 
-    let r = parser
-        .run_inner(Args::from(&["--", "a"]).set_comp(0))
-        .unwrap_err()
-        .unwrap_stdout();
-    assert_eq!(r, "alpha"); // this is not a valid positional
-                            //
     let r = parser
         .run_inner(Args::from(&["--"]).set_comp(0))
         .unwrap_err()