Repository: clap-rs/clap (language: rust)

Issue:
Subcommand flags are separated by spaces while argument flags use commas
### Please complete the following tasks

- [X] I have searched the [discussions](https://github.com/clap-rs/clap/discussions)
- [X] I have searched the [open](https://github.com/clap-rs/clap/issues) and [rejected](https://github.com/clap-rs/clap/issues?q=is%3Aissue+label%3AS-wont-fix+is%3Aclosed) issues

### Rust Version

1.63.0

### Clap Version

3.2.22

### Minimal reproducible code

```rust
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[clap()]
struct Args {
    #[clap(short)]
    arg: String,

    #[clap(subcommand)]
    subcommand: Command,
}

#[derive(Subcommand)]
enum Command {
    #[clap(name = "test", short_flag = 't', long_flag = "test")]
    Test,
}

fn main() {
    let _args = Args::parse();
}
```


### Steps to reproduce the bug with the above code

cargo run

### Actual Behaviour

The help output is
```
clap-test 

USAGE:
    clap-test -a <ARG> <SUBCOMMAND>

OPTIONS:
    -a <ARG>        
    -h, --help      Print help information

SUBCOMMANDS:
    help              Print this message or the help of the given subcommand(s)
    test -t --test    
```
Which separates the options-flags by comma while the subcommand flags are just separated by space. This behaviour is inconsistent. I personally would prefer to use comma for separation in both cases as it is easier to understand.

### Expected Behaviour

The help output should look like this:
```rust
clap-test 

USAGE:
    clap-test -a <ARG> <SUBCOMMAND>

OPTIONS:
    -a <ARG>        
    -h, --help      Print help information

SUBCOMMANDS:
    help              Print this message or the help of the given subcommand(s)
    test, -t, --test    
```

### Additional Context

This issue relates to https://github.com/crystal-linux/amethyst/issues/64

### Debug Output

_No response_


Discussion / hints:

Fix patch:
diff --git a/examples/pacman.md b/examples/pacman.md
index f75ac172d7f..73ec86fafa1 100644
--- a/examples/pacman.md
+++ b/examples/pacman.md
@@ -40,9 +40,9 @@ package manager utility
 Usage: pacman[EXE] <COMMAND>
 
 Commands:
-  query -Q --query  Query the package database.
-  sync -S --sync    Synchronize packages.
-  help              Print this message or the help of the given subcommand(s)
+  query, -Q, --query  Query the package database.
+  sync, -S, --sync    Synchronize packages.
+  help                Print this message or the help of the given subcommand(s)
 
 Options:
   -h, --help     Print help information
diff --git a/src/builder/styled_str.rs b/src/builder/styled_str.rs
index 61aad469d1d..503bf333747 100644
--- a/src/builder/styled_str.rs
+++ b/src/builder/styled_str.rs
@@ -219,6 +219,24 @@ impl From<&'_ &'static str> for StyledStr {
     }
 }
 
+impl PartialOrd for StyledStr {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for StyledStr {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.iter().map(cmp_key).cmp(other.iter().map(cmp_key))
+    }
+}
+
+fn cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str) {
+    let style = c.0.map(|s| s.as_usize());
+    let content = c.1;
+    (style, content)
+}
+
 /// Color-unaware printing. Never uses coloring.
 impl std::fmt::Display for StyledStr {
     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
@@ -240,3 +258,17 @@ pub(crate) enum Style {
     Error,
     Hint,
 }
+
+impl Style {
+    fn as_usize(&self) -> usize {
+        match self {
+            Self::Header => 0,
+            Self::Literal => 1,
+            Self::Placeholder => 2,
+            Self::Good => 3,
+            Self::Warning => 4,
+            Self::Error => 5,
+            Self::Hint => 6,
+        }
+    }
+}
diff --git a/src/output/help.rs b/src/output/help.rs
index c69920f1647..f20ccc9e21f 100644
--- a/src/output/help.rs
+++ b/src/output/help.rs
@@ -1,7 +1,6 @@
 // Std
 use std::borrow::Cow;
 use std::cmp;
-use std::fmt::Write as _;
 use std::usize;
 
 // Internal
@@ -816,16 +815,18 @@ impl<'cmd, 'writer> Help<'cmd, 'writer> {
             .get_subcommands()
             .filter(|subcommand| should_show_subcommand(subcommand))
         {
-            let mut sc_str = String::new();
-            sc_str.push_str(subcommand.get_name());
+            let mut styled = StyledStr::new();
+            styled.literal(subcommand.get_name());
             if let Some(short) = subcommand.get_short_flag() {
-                write!(sc_str, " -{}", short).unwrap();
+                styled.none(", ");
+                styled.literal(format!("-{}", short));
             }
             if let Some(long) = subcommand.get_long_flag() {
-                write!(sc_str, " --{}", long).unwrap();
+                styled.none(", ");
+                styled.literal(format!("--{}", long));
             }
-            longest = longest.max(display_width(&sc_str));
-            ord_v.push((subcommand.get_display_order(), sc_str, subcommand));
+            longest = longest.max(styled.display_width());
+            ord_v.push((subcommand.get_display_order(), styled, subcommand));
         }
         ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1)));
 
@@ -834,7 +835,7 @@ impl<'cmd, 'writer> Help<'cmd, 'writer> {
         let next_line_help = self.will_subcommands_wrap(cmd.get_subcommands(), longest);
 
         let mut first = true;
-        for (_, sc_str, sc) in &ord_v {
+        for (_, sc_str, sc) in ord_v {
             if first {
                 first = false;
             } else {
@@ -861,7 +862,7 @@ impl<'cmd, 'writer> Help<'cmd, 'writer> {
 
     fn write_subcommand(
         &mut self,
-        sc_str: &str,
+        sc_str: StyledStr,
         cmd: &Command,
         next_line_help: bool,
         longest: usize,
@@ -921,11 +922,12 @@ impl<'cmd, 'writer> Help<'cmd, 'writer> {
     }
 
     /// Writes subcommand to the wrapped stream.
-    fn subcmd(&mut self, sc_str: &str, next_line_help: bool, longest: usize) {
+    fn subcmd(&mut self, sc_str: StyledStr, next_line_help: bool, longest: usize) {
+        let width = sc_str.display_width();
+
         self.none(TAB);
-        self.literal(sc_str);
+        self.writer.extend(sc_str.into_iter());
         if !next_line_help {
-            let width = display_width(sc_str);
             self.spaces(width.max(longest + TAB_WIDTH) - width);
         }
     }


Test patch: