Repository: clap-rs/clap
Pull request #5879 (2025-01-09 17:43:23)

Replace another sort_by call by a manual insertion with a binary_search
Turns out to be another win by doing a manual insertion sort of the suggestion rather than using `sort_by`.

Before:

```
# cargo bloat --release --example git
    Finished `release` profile [optimized] target(s) in 0.05s
    Analyzing target/release/examples/git

 File  .text     Size        Crate Name
 1.6%   2.8%  15.2KiB          git git::main
 1.5%   2.7%  14.8KiB clap_builder clap_builder::parser::parser::Parser::get_matches_with
 1.5%   2.6%  14.1KiB clap_builder clap_builder::builder::command::Command::_build_self
 1.0%   1.7%   9.4KiB          std std::backtrace_rs::symbolize::gimli::resolve
 0.9%   1.6%   8.6KiB          std std::backtrace_rs::symbolize::gimli::Context::new
 0.7%   1.3%   7.1KiB          std gimli::read::dwarf::Unit<R>::new
 0.7%   1.3%   7.1KiB    [Unknown] __mh_execute_header
 0.7%   1.3%   7.0KiB clap_builder <clap_builder::error::format::RichFormatter as clap_builder::error::format::ErrorFormatter>::format_error
 0.7%   1.2%   6.4KiB clap_builder clap_builder::parser::validator::Validator::validate
 0.6%   1.1%   5.9KiB          std addr2line::ResUnit<R>::find_function_or_location::{{closure}}
 0.6%   1.1%   5.9KiB clap_builder <alloc::vec::Vec<T,A> as core::clone::Clone>::clone
 0.6%   1.0%   5.4KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_all_args
 0.5%   1.0%   5.2KiB clap_builder clap_builder::output::usage::Usage::write_arg_usage
 0.5%   0.9%   5.1KiB          std addr2line::Lines::parse
 0.5%   0.9%   5.1KiB clap_builder clap_builder::parser::parser::Parser::react
 0.5%   0.8%   4.5KiB clap_builder clap_builder::output::usage::Usage::write_usage_no_title
 0.5%   0.8%   4.4KiB clap_builder clap_builder::parser::parser::Parser::parse_help_subcommand
 0.4%   0.8%   4.1KiB          std addr2line::function::Function<R>::parse_children
 0.4%   0.8%   4.1KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_templated_help
 0.4%   0.7%   3.9KiB          std gimli::read::unit::parse_attribute
42.6%  75.2% 408.9KiB              And 1183 smaller methods. Use -n N to show more.
56.7% 100.0% 544.0KiB              .text section size, the file size is 960.0KiB
```

and after:

```
# cargo bloat --release --example git
    Finished `release` profile [optimized] target(s) in 0.05s
    Analyzing target/release/examples/git

  File  .text     Size        Crate Name
 1.6%   2.9%  15.2KiB          git git::main
 1.6%   2.8%  14.8KiB clap_builder clap_builder::parser::parser::Parser::get_matches_with
 1.5%   2.6%  14.1KiB clap_builder clap_builder::builder::command::Command::_build_self
 1.0%   1.8%   9.4KiB          std std::backtrace_rs::symbolize::gimli::resolve
 0.9%   1.6%   8.6KiB          std std::backtrace_rs::symbolize::gimli::Context::new
 0.8%   1.3%   7.1KiB          std gimli::read::dwarf::Unit<R>::new
 0.7%   1.3%   7.0KiB clap_builder <clap_builder::error::format::RichFormatter as clap_builder::error::format::ErrorFormatter>::format_error
 0.7%   1.2%   6.1KiB clap_builder clap_builder::parser::validator::Validator::validate
 0.6%   1.1%   5.9KiB          std addr2line::ResUnit<R>::find_function_or_location::{{closure}}
 0.6%   1.1%   5.9KiB clap_builder <alloc::vec::Vec<T,A> as core::clone::Clone>::clone
 0.6%   1.1%   5.8KiB    [Unknown] __mh_execute_header
 0.6%   1.0%   5.4KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_all_args
 0.6%   1.0%   5.2KiB clap_builder clap_builder::output::usage::Usage::write_arg_usage
 0.5%   1.0%   5.1KiB          std addr2line::Lines::parse
 0.5%   1.0%   5.1KiB clap_builder clap_builder::parser::parser::Parser::react
 0.5%   0.8%   4.5KiB clap_builder clap_builder::output::usage::Usage::write_usage_no_title
 0.5%   0.8%   4.4KiB clap_builder clap_builder::parser::parser::Parser::parse_help_subcommand
 0.4%   0.8%   4.1KiB          std addr2line::function::Function<R>::parse_children
 0.4%   0.8%   4.1KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_templated_help
 0.4%   0.7%   3.9KiB          std gimli::read::unit::parse_attribute
42.1%  74.6% 396.4KiB              And 1163 smaller methods. Use -n N to show more.
56.4% 100.0% 531.3KiB              .text section size, the file size is 942.1KiB
```

therealprof: Argh, forgot to run the unit tests on the last change which changed the order of the results in a test for some reason. Will fix or revert to previous version.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index d249bd63..ab60abae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -474,7 +474,7 @@ dependencies = [
 
 [[package]]
 name = "clap_complete"
-version = "4.5.42"
+version = "4.5.41"
 dependencies = [
  "automod",
  "clap 4.5.25",
diff --git a/clap_builder/src/parser/features/suggestions.rs b/clap_builder/src/parser/features/suggestions.rs
index b8bb7adf..65340203 100644
--- a/clap_builder/src/parser/features/suggestions.rs
+++ b/clap_builder/src/parser/features/suggestions.rs
@@ -1,6 +1,3 @@
-#[cfg(feature = "suggestions")]
-use std::cmp::Ordering;
-
 // Internal
 use crate::builder::Command;
 
@@ -13,15 +10,29 @@ where
     T: AsRef<str>,
     I: IntoIterator<Item = T>,
 {
-    let mut candidates: Vec<(f64, String)> = possible_values
-        .into_iter()
+    use std::cmp::Ordering;
+
+    let mut candidates: Vec<(f64, String)> = Vec::new();
+    for pv in possible_values {
         // GH #4660: using `jaro` because `jaro_winkler` implementation in `strsim-rs` is wrong
         // causing strings with common prefix >=10 to be considered perfectly similar
-        .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned()))
-        // Confidence of 0.7 so that bar -> baz is suggested
-        .filter(|(confidence, _)| *confidence > 0.7)
-        .collect();
-    candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
+        let confidence = strsim::jaro(v, pv.as_ref());
+
+        if confidence > 0.7 {
+            let new_elem = (confidence, pv.as_ref().to_owned());
+            let pos = candidates
+                .binary_search_by(|probe| {
+                    if probe.0 > confidence {
+                        Ordering::Greater
+                    } else {
+                        Ordering::Less
+                    }
+                })
+                .unwrap_or_else(|e| e);
+            candidates.insert(pos, new_elem);
+        }
+    }
+
     candidates.into_iter().map(|(_, pv)| pv).collect()
 }
 
diff --git a/clap_complete/CHANGELOG.md b/clap_complete/CHANGELOG.md
index 2cbaf557..ce4d48b4 100644
--- a/clap_complete/CHANGELOG.md
+++ b/clap_complete/CHANGELOG.md
@@ -7,16 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 <!-- next-header -->
 ## [Unreleased] - ReleaseDate
 
-## [4.5.42] - 2025-01-09
-
-### Fixes
-
-- *(fish)* Corectly generate `aot` completions for single-value `ValueEnum`s
-
 ## [4.5.41] - 2025-01-07
 
-### Fixes
-
 - *(elvish)* Avoid leaking env variables in dynamic completions
 
 ## [4.5.40] - 2024-12-17
@@ -510,8 +502,7 @@ MSRV changed to 1.64.0
 ## [3.0.1] - 2022-01-03
 
 <!-- next-url -->
-[Unreleased]: https://github.com/clap-rs/clap/compare/clap_complete-v4.5.42...HEAD
-[4.5.42]: https://github.com/clap-rs/clap/compare/clap_complete-v4.5.41...clap_complete-v4.5.42
+[Unreleased]: https://github.com/clap-rs/clap/compare/clap_complete-v4.5.41...HEAD
 [4.5.41]: https://github.com/clap-rs/clap/compare/clap_complete-v4.5.40...clap_complete-v4.5.41
 [4.5.40]: https://github.com/clap-rs/clap/compare/clap_complete-v4.5.39...clap_complete-v4.5.40
 [4.5.39]: https://github.com/clap-rs/clap/compare/clap_complete-v4.5.38...clap_complete-v4.5.39
diff --git a/clap_complete/Cargo.toml b/clap_complete/Cargo.toml
index 08934f18..1cc5ccd6 100644
--- a/clap_complete/Cargo.toml
+++ b/clap_complete/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "clap_complete"
-version = "4.5.42"
+version = "4.5.41"
 description = "Generate shell completion scripts for your clap::Command"
 categories = ["command-line-interface"]
 keywords = [
diff --git a/clap_complete/README.md b/clap_complete/README.md
index ed8df6a8..696f7199 100644
--- a/clap_complete/README.md
+++ b/clap_complete/README.md
@@ -5,16 +5,16 @@
 
 [![Crates.io](https://img.shields.io/crates/v/clap_complete?style=flat-square)](https://crates.io/crates/clap_complete)
 [![Crates.io](https://img.shields.io/crates/d/clap_complete?style=flat-square)](https://crates.io/crates/clap_complete)
-[![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](https://github.com/clap-rs/clap/blob/clap_complete-v4.5.42/LICENSE-APACHE)
-[![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](https://github.com/clap-rs/clap/blob/clap_complete-v4.5.42/LICENSE-MIT)
+[![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](https://github.com/clap-rs/clap/blob/clap_complete-v4.5.41/LICENSE-APACHE)
+[![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](https://github.com/clap-rs/clap/blob/clap_complete-v4.5.41/LICENSE-MIT)
 
 Dual-licensed under [Apache 2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT).
 
 1. [About](#about)
 2. [API Reference](https://docs.rs/clap_complete)
 3. [Questions & Discussions](https://github.com/clap-rs/clap/discussions)
-4. [CONTRIBUTING](https://github.com/clap-rs/clap/blob/clap_complete-v4.5.42/clap_complete/CONTRIBUTING.md)
-5. [Sponsors](https://github.com/clap-rs/clap/blob/clap_complete-v4.5.42/README.md#sponsors)
+4. [CONTRIBUTING](https://github.com/clap-rs/clap/blob/clap_complete-v4.5.41/clap_complete/CONTRIBUTING.md)
+5. [Sponsors](https://github.com/clap-rs/clap/blob/clap_complete-v4.5.41/README.md#sponsors)
 
 ## About
 
diff --git a/clap_complete/src/aot/shells/fish.rs b/clap_complete/src/aot/shells/fish.rs
index 0c6404a9..647e856f 100644
--- a/clap_complete/src/aot/shells/fish.rs
+++ b/clap_complete/src/aot/shells/fish.rs
@@ -275,10 +275,10 @@ fn value_completion(option: &Arg) -> String {
     }
 
     if let Some(data) = utils::possible_values(option) {
-        // We return the possible values with their own empty description e.g. "a\t''\nb\t''"
+        // We return the possible values with their own empty description e.g. {a\t,b\t}
         // this makes sure that a and b don't get the description of the option or argument
         format!(
-            " -r -f -a \"{}\"",
+            " -r -f -a \"{{{}}}\"",
             data.iter()
                 .filter_map(|value| if value.is_hide_set() {
                     None
@@ -292,7 +292,7 @@ fn value_completion(option: &Arg) -> String {
                     ))
                 })
                 .collect::<Vec<_>>()
-                .join("\n")
+                .join(",")
         )
     } else {
         // NB! If you change this, please also update the table in `ValueHint` documentation.


Test patch:
diff --git a/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish b/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish
index a45106e8..926edab8 100644
--- a/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish
+++ b/clap_complete/tests/snapshots/home/static/exhaustive/fish/fish/completions/exhaustive.fish
@@ -24,12 +24,8 @@ function __fish_exhaustive_using_subcommand
 	contains -- $cmd[1] $argv
 end
 
-complete -c exhaustive -n "__fish_exhaustive_needs_command" -l generate -d 'generate' -r -f -a "bash\t''
-elvish\t''
-fish\t''
-powershell\t''
-zsh\t''"
-complete -c exhaustive -n "__fish_exhaustive_needs_command" -l empty-choice -r -f -a ""
+complete -c exhaustive -n "__fish_exhaustive_needs_command" -l generate -d 'generate' -r -f -a "{bash\t'',elvish\t'',fish\t'',powershell\t'',zsh\t''}"
+complete -c exhaustive -n "__fish_exhaustive_needs_command" -l empty-choice -r -f -a "{}"
 complete -c exhaustive -n "__fish_exhaustive_needs_command" -s h -l help -d 'Print help'
 complete -c exhaustive -n "__fish_exhaustive_needs_command" -f -a "empty"
 complete -c exhaustive -n "__fish_exhaustive_needs_command" -f -a "global"
@@ -59,15 +55,11 @@ complete -c exhaustive -n "__fish_exhaustive_using_subcommand global; and __fish
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand global; and __fish_seen_subcommand_from help" -f -a "two"
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand global; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand action" -l set -d 'value' -r
-complete -c exhaustive -n "__fish_exhaustive_using_subcommand action" -l choice -d 'enum' -r -f -a "first\t''
-second\t''"
+complete -c exhaustive -n "__fish_exhaustive_using_subcommand action" -l choice -d 'enum' -r -f -a "{first\t'',second\t''}"
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand action" -l set-true -d 'bool'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand action" -l count -d 'number'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand action" -s h -l help -d 'Print help'
-complete -c exhaustive -n "__fish_exhaustive_using_subcommand quote; and not __fish_seen_subcommand_from cmd-single-quotes cmd-double-quotes cmd-backticks cmd-backslash cmd-brackets cmd-expansions escape-help help" -l choice -r -f -a "another shell\t'something with a space'
-bash\t'bash (shell)'
-fish\t'fish shell'
-zsh\t'zsh shell'"
+complete -c exhaustive -n "__fish_exhaustive_using_subcommand quote; and not __fish_seen_subcommand_from cmd-single-quotes cmd-double-quotes cmd-backticks cmd-backslash cmd-brackets cmd-expansions escape-help help" -l choice -r -f -a "{another shell\t'something with a space',bash\t'bash (shell)',fish\t'fish shell',zsh\t'zsh shell'}"
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand quote; and not __fish_seen_subcommand_from cmd-single-quotes cmd-double-quotes cmd-backticks cmd-backslash cmd-brackets cmd-expansions escape-help help" -l single-quotes -d 'Can be \'always\', \'auto\', or \'never\''
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand quote; and not __fish_seen_subcommand_from cmd-single-quotes cmd-double-quotes cmd-backticks cmd-backslash cmd-brackets cmd-expansions escape-help help" -l double-quotes -d 'Can be "always", "auto", or "never"'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand quote; and not __fish_seen_subcommand_from cmd-single-quotes cmd-double-quotes cmd-backticks cmd-backslash cmd-brackets cmd-expansions escape-help help" -l backticks -d 'For more information see `echo test`'
@@ -115,9 +107,7 @@ complete -c exhaustive -n "__fish_exhaustive_using_subcommand last" -s h -l help
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand alias" -s o -s O -l option -l opt -d 'cmd option' -r
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand alias" -s f -s F -l flag -l flg -d 'cmd flag'
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand alias" -s h -l help -d 'Print help'
-complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -l choice -r -f -a "bash\t''
-fish\t''
-zsh\t''"
+complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -l choice -r -f -a "{bash\t'',fish\t'',zsh\t''}"
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -l unknown -r
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -l other -r -f
 complete -c exhaustive -n "__fish_exhaustive_using_subcommand hint" -s p -l path -r -F
diff --git a/clap_complete/tests/snapshots/sub_subcommands.fish b/clap_complete/tests/snapshots/sub_subcommands.fish
index 41ca7cec..62afbe5c 100644
--- a/clap_complete/tests/snapshots/sub_subcommands.fish
+++ b/clap_complete/tests/snapshots/sub_subcommands.fish
@@ -38,8 +38,7 @@ complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and not __fish_s
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and not __fish_seen_subcommand_from sub_cmd help" -s V -l version -d 'Print version'
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and not __fish_seen_subcommand_from sub_cmd help" -f -a "sub_cmd" -d 'sub-subcommand'
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and not __fish_seen_subcommand_from sub_cmd help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
-complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and __fish_seen_subcommand_from sub_cmd" -l config -d 'the other case to test' -r -f -a "Lest quotes\, aren\'t escaped.\t'help,with,comma'
-Second to trigger display of options\t''"
+complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and __fish_seen_subcommand_from sub_cmd" -l config -d 'the other case to test' -r -f -a "{Lest quotes\, aren\'t escaped.\t'help,with,comma',Second to trigger display of options\t''}"
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and __fish_seen_subcommand_from sub_cmd" -s h -l help -d 'Print help (see more with \'--help\')'
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and __fish_seen_subcommand_from sub_cmd" -s V -l version -d 'Print version'
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd; and __fish_seen_subcommand_from help" -f -a "sub_cmd" -d 'sub-subcommand'
@@ -48,8 +47,7 @@ complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and not __
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and not __fish_seen_subcommand_from sub_cmd help" -s V -l version -d 'Print version'
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and not __fish_seen_subcommand_from sub_cmd help" -f -a "sub_cmd" -d 'sub-subcommand'
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and not __fish_seen_subcommand_from sub_cmd help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
-complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and __fish_seen_subcommand_from sub_cmd" -l config -d 'the other case to test' -r -f -a "Lest quotes\, aren\'t escaped.\t'help,with,comma'
-Second to trigger display of options\t''"
+complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and __fish_seen_subcommand_from sub_cmd" -l config -d 'the other case to test' -r -f -a "{Lest quotes\, aren\'t escaped.\t'help,with,comma',Second to trigger display of options\t''}"
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and __fish_seen_subcommand_from sub_cmd" -s h -l help -d 'Print help (see more with \'--help\')'
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and __fish_seen_subcommand_from sub_cmd" -s V -l version -d 'Print version'
 complete -c my-app -n "__fish_my_app_using_subcommand some_cmd_alias; and __fish_seen_subcommand_from help" -f -a "sub_cmd" -d 'sub-subcommand'
diff --git a/clap_complete/tests/snapshots/value_hint.fish b/clap_complete/tests/snapshots/value_hint.fish
index a298833d..acddbf88 100644
--- a/clap_complete/tests/snapshots/value_hint.fish
+++ b/clap_complete/tests/snapshots/value_hint.fish
@@ -1,6 +1,4 @@
-complete -c my-app -l choice -r -f -a "bash\t''
-fish\t''
-zsh\t''"
+complete -c my-app -l choice -r -f -a "{bash\t'',fish\t'',zsh\t''}"
 complete -c my-app -l unknown -r
 complete -c my-app -l other -r -f
 complete -c my-app -s p -l path -r -F
diff --git a/clap_complete/tests/testsuite/fish.rs b/clap_complete/tests/testsuite/fish.rs
index 0b4d3ece..a788debb 100644
--- a/clap_complete/tests/testsuite/fish.rs
+++ b/clap_complete/tests/testsuite/fish.rs
@@ -187,7 +187,7 @@ CHANGELOG.md  examples/        LICENSE-MIT     src/
     let actual = runtime.complete(input, &term).unwrap();
     let expected = snapbox::str![[r#"
 % exhaustive quote --choice 
-another  bash  (bash (shell))  fish  (fish shell)  shell  (something with a space)  zsh  (zsh shell)
+another shell  (something with a space)  bash  (bash (shell))  fish  (fish shell)  zsh  (zsh shell)
 "#]];
     assert_data_eq!(actual, expected);
 }