Repository: clap-rs/clap (rust)

Issue:
Markdown links in the help message of a value enum causes ZSH completion to error
### 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.67.1

### Clap Version

4.1.8

### Minimal reproducible code

The complete code is here: https://github.com/KSXGitHub/bug-report-clap-completion-when-help-contains-special-characters

### Steps to reproduce the bug with the above code

The complete steps to reproduce is also here: https://github.com/KSXGitHub/bug-report-clap-completion-when-help-contains-special-characters

### Actual Behaviour

The following error messages appear:

```
(eval):1: unmatched "
_arguments:465: command not found: (
(eval):1: unmatched "
_arguments:465: command not found: (
(eval):1: unmatched "
_arguments:465: command not found: (
```

### Expected Behaviour

Completion works normally.

### Additional Context

_No response_

### Debug Output

_No response_

Pull request:
fix(complete): Escape special characters properly for zsh
Closes #4751.

The original problem is because of unescaped colons, which is included in this PR. However, there are other characters that might cause similar issues, such as backticks and dollar signs. I don't think one would expect something like `$PATH` to be expanded when he or she puts it in a helper string.

Added a few test cases for unit testing. I think we should add integration tests for this, i.e., help strings from comments, but I'm not sure how to do it.

Patch:
diff --git a/clap_complete/src/shells/zsh.rs b/clap_complete/src/shells/zsh.rs
index 3150cac9..28c8ff85 100644
--- a/clap_complete/src/shells/zsh.rs
+++ b/clap_complete/src/shells/zsh.rs
@@ -426,6 +426,9 @@ fn escape_help(string: &str) -> String {
         .replace('\'', "'\\''")
         .replace('[', "\\[")
         .replace(']', "\\]")
+        .replace(':', "\\:")
+        .replace('$', "\\$")
+        .replace('`', "\\`")
 }
 
 /// Escape value string inside single quotes and parentheses
@@ -433,6 +436,11 @@ fn escape_value(string: &str) -> String {
     string
         .replace('\\', "\\\\")
         .replace('\'', "'\\''")
+        .replace('[', "\\[")
+        .replace(']', "\\]")
+        .replace(':', "\\:")
+        .replace('$', "\\$")
+        .replace('`', "\\`")
         .replace('(', "\\(")
         .replace(')', "\\)")
         .replace(' ', "\\ ")
@@ -688,3 +696,26 @@ fn write_positionals_of(p: &Command) -> String {
 
     ret.join("\n")
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::shells::zsh::{escape_help, escape_value};
+
+    #[test]
+    fn test_escape_value() {
+        let raw_string = "\\ [foo]() `bar https://$PATH";
+        assert_eq!(
+            escape_value(raw_string),
+            "\\\\\\ \\[foo\\]\\(\\)\\ \\`bar\\ https\\://\\$PATH"
+        )
+    }
+
+    #[test]
+    fn test_escape_help() {
+        let raw_string = "\\ [foo]() `bar https://$PATH";
+        assert_eq!(
+            escape_help(raw_string),
+            "\\\\ \\[foo\\]() \\`bar https\\://\\$PATH"
+        )
+    }
+}


Test patch:
diff --git a/clap_complete/tests/snapshots/quoting.zsh b/clap_complete/tests/snapshots/quoting.zsh
index 95ed7eb6..7e36d721 100644
--- a/clap_complete/tests/snapshots/quoting.zsh
+++ b/clap_complete/tests/snapshots/quoting.zsh
@@ -17,10 +17,10 @@ _my-app() {
     _arguments "${_arguments_options[@]}" \
 '--single-quotes[Can be '\''always'\'', '\''auto'\'', or '\''never'\'']' \
 '--double-quotes[Can be "always", "auto", or "never"]' \
-'--backticks[For more information see `echo test`]' \
+'--backticks[For more information see \`echo test\`]' \
 '--backslash[Avoid '\''\\n'\'']' \
 '--brackets[List packages \[filter\]]' \
-'--expansions[Execute the shell command with $SHELL]' \
+'--expansions[Execute the shell command with \$SHELL]' \
 '-h[Print help]' \
 '--help[Print help]' \
 '-V[Print version]' \
@@ -124,10 +124,10 @@ _my-app_commands() {
     local commands; commands=(
 'cmd-single-quotes:Can be '\''always'\'', '\''auto'\'', or '\''never'\''' \
 'cmd-double-quotes:Can be "always", "auto", or "never"' \
-'cmd-backticks:For more information see `echo test`' \
+'cmd-backticks:For more information see \`echo test\`' \
 'cmd-backslash:Avoid '\''\\n'\''' \
 'cmd-brackets:List packages \[filter\]' \
-'cmd-expansions:Execute the shell command with $SHELL' \
+'cmd-expansions:Execute the shell command with \$SHELL' \
 'help:Print this message or the help of the given subcommand(s)' \
     )
     _describe -t commands 'my-app commands' commands "$@"
@@ -197,10 +197,10 @@ _my-app__help_commands() {
     local commands; commands=(
 'cmd-single-quotes:Can be '\''always'\'', '\''auto'\'', or '\''never'\''' \
 'cmd-double-quotes:Can be "always", "auto", or "never"' \
-'cmd-backticks:For more information see `echo test`' \
+'cmd-backticks:For more information see \`echo test\`' \
 'cmd-backslash:Avoid '\''\\n'\''' \
 'cmd-brackets:List packages \[filter\]' \
-'cmd-expansions:Execute the shell command with $SHELL' \
+'cmd-expansions:Execute the shell command with \$SHELL' \
 'help:Print this message or the help of the given subcommand(s)' \
     )
     _describe -t commands 'my-app help commands' commands "$@"