Repository: sigoden/argc (Rust)

Issue:
Completions issue with extended documentation
Hello! Thank you for adding extended documentation in #97. I've begun migrating [my dotfiles](https://github.com/CGamesPlay/dotfiles/blob/master/files/.local/bin/.argc) to them and noticed a minor problem with the generated shell completions.

If you run `argc --argc-compgen fish` on the linked file, you get this output:
```
create-docker-workspace	Copy the docker workspace root template to the named directory.
traefik	Start an automatic local vhost for Docker containers.
The traefik container will run permanently. The dashboard will be available
at the root domain. All docker containers will be made available at URLs
according to their compose configuration, by default:

    http://SERVICE-PROJECT.lvh.me/

- Docker label reference: https://doc.traefik.io/traefik/providers/docker/
create-argcfile	Create a sample Argcfile.
```

As you can see, the extended documentation is included in the completion output, which causes each line to appear as a separate (invalid) completion. This also happens for zsh.


Discussion:

Patch:
diff --git a/src/command/mod.rs b/src/command/mod.rs
index 5e278880..4d031798 100644
--- a/src/command/mod.rs
+++ b/src/command/mod.rs
@@ -435,10 +435,7 @@ impl Command {
     }
 
     pub(crate) fn render_subcommand_describe(&self) -> String {
-        let mut output = match self.describe.split_once('\n') {
-            Some((v, _)) => v.to_string(),
-            None => self.describe.clone(),
-        };
+        let mut output = self.describe_head().to_string();
         if self.aliases.is_empty() {
             return output;
         } else {
@@ -450,6 +447,13 @@ impl Command {
         output
     }
 
+    pub(crate) fn describe_head(&self) -> &str {
+        match self.describe.split_once('\n') {
+            Some((v, _)) => v,
+            None => self.describe.as_str(),
+        }
+    }
+
     pub(crate) fn list_names(&self) -> Vec<String> {
         let mut output = vec![self.name.clone().unwrap_or_default()];
         output.extend(self.aliases.to_vec());
diff --git a/src/matcher.rs b/src/matcher.rs
index 1c571dd2..b45639e0 100644
--- a/src/matcher.rs
+++ b/src/matcher.rs
@@ -664,9 +664,9 @@ For more information, try '--help'.
         for param in cmd.flag_option_params.iter() {
             let exist = args.contains(param.name.as_str());
             if !exist || param.multiple {
-                let describe = param.describe.clone();
+                let describe = param.describe_head();
                 for v in param.list_names() {
-                    output.push((v, describe.clone()))
+                    output.push((v, describe.to_string()))
                 }
             }
         }
@@ -751,9 +751,9 @@ fn comp_subcommands_positional(cmd: &Command, values: &[Vec<&str>]) -> Vec<(Stri
 fn comp_subcomands(cmd: &Command) -> Vec<(String, String)> {
     let mut output = vec![];
     for subcmd in cmd.subcommands.iter() {
-        let describe = subcmd.describe.clone();
+        let describe = subcmd.describe_head();
         for v in subcmd.list_names() {
-            output.push((v, describe.clone()))
+            output.push((v, describe.to_string()))
         }
     }
     output
@@ -766,7 +766,7 @@ fn comp_flag_option(param: &FlagOptionParam, index: usize) -> Vec<(String, Strin
         .map(|v| v.as_str())
         .unwrap_or_else(|| param.arg_value_names.last().unwrap());
     comp_param(
-        &param.describe,
+        param.describe_head(),
         value_name,
         &param.choices,
         &param.choices_fn,
@@ -777,7 +777,7 @@ fn comp_flag_option(param: &FlagOptionParam, index: usize) -> Vec<(String, Strin
 
 fn comp_positional(param: &PositionalParam) -> Vec<(String, String)> {
     comp_param(
-        &param.describe,
+        param.describe_head(),
         &param.arg_value_name,
         &param.choices,
         &param.choices_fn,
diff --git a/src/param.rs b/src/param.rs
index 551c1945..9a49d7c8 100644
--- a/src/param.rs
+++ b/src/param.rs
@@ -284,6 +284,13 @@ impl FlagOptionParam {
             self.arg_value_names.len()
         }
     }
+
+    pub(crate) fn describe_head(&self) -> &str {
+        match self.describe.split_once('\n') {
+            Some((v, _)) => v,
+            None => self.describe.as_str(),
+        }
+    }
 }
 
 #[derive(Debug, PartialEq, Eq, Clone, Serialize)]
@@ -378,6 +385,13 @@ impl PositionalParam {
             Some(ArgcValue::PositionalSingle(name, must_get_first(values)))
         }
     }
+
+    pub(crate) fn describe_head(&self) -> &str {
+        match self.describe.split_once('\n') {
+            Some((v, _)) => v,
+            None => self.describe.as_str(),
+        }
+    }
 }
 
 fn render_name(


Test patch:
diff --git a/tests/compgen.rs b/tests/compgen.rs
index c7bee975..afeab877 100644
--- a/tests/compgen.rs
+++ b/tests/compgen.rs
@@ -171,3 +171,20 @@ fn option_multi_vals() {
 "###;
     snapshot_compgen!(script, vec![" --oa ", " --oa bash ", " --oa bash cmd1 ",]);
 }
+
+#[test]
+fn multiline_doc() {
+    let script = r###"
+# @cmd cmd-line1
+# cmd-line2
+# @option --foo option-line1
+# option-line2
+# @arg bar bar-line1
+# bar-line2
+cmda() { :; }
+
+# @cmd line
+cmdb() { :; }
+"###;
+    snapshot_compgen!(script, vec![" ", "cmda ",]);
+}
diff --git a/tests/snapshots/integration__compgen__multiline_doc.snap b/tests/snapshots/integration__compgen__multiline_doc.snap
new file mode 100644
index 00000000..e2cbdf8e
--- /dev/null
+++ b/tests/snapshots/integration__compgen__multiline_doc.snap
@@ -0,0 +1,13 @@
+---
+source: tests/compgen.rs
+expression: data
+---
+************ COMPGEN `prog  ` ************
+cmda	cmd-line1
+cmdb	line
+
+************ COMPGEN `prog cmda ` ************
+--foo	option-line1
+[BAR]	bar-line1
+
+