Repository: hamdanal/rich-argparse

Issue:
Formatting of arguments applied even within back-ticks (syntax style)
This issue relates to the priority in applying the default styles, defined in https://github.com/hamdanal/rich-argparse?tab=readme-ov-file#output-styles as:

```python
{
    'argparse.args': 'cyan',  # for positional-arguments and --options (e.g "--help")
    'argparse.groups': 'dark_orange',  # for group names (e.g. "positional arguments")
    'argparse.help': 'default',  # for argument's help text (e.g. "show this help message and exit")
    'argparse.metavar': 'dark_cyan',  # for metavariables (e.g. "FILE" in "--file FILE")
    'argparse.prog': 'grey50',  # for %(prog)s in the usage (e.g. "foo" in "Usage: foo [options]")
    'argparse.syntax': 'bold',  # for highlights of back-tick quoted text (e.g. "`some text`")
    'argparse.text': 'default',  # for descriptions, epilog, and --version (e.g. "A program to foo")
    'argparse.default': 'italic',  # for %(default)s in the help (e.g. "Value" in "(default: Value)")
}
```

After coming up with https://github.com/peterjc/thapbi-pict/commit/9ef702268331cbeba9e558583e6fa6a97830c0b7 I wonder if this is actually a bug. Consider the following argument:

```python
    subcommand_parser.add_argument(
        "--denoise",  # Named to match sample-tally and pipeline setting
        choices=["unoise-l", "usearch", "vsearch"],
        default="unoise-l",
        help="Choice of read-correction algorithm, default 'unoise-l' for "
        "built-in reimplementation of the Edgar (2016) UNOISE algorithm "
        "using Levenshtein distance. Use 'usearch' or 'vsearch' to call "
        "external tools `usearch -unoise3 ...` or `vsearch --cluster_unoise "
        "...` respectively.",
    )
```

In this example, this part is explicitly a syntax block using backticks, which would be bold by default via the "argparse.syntax" style:

```
`usearch -unoise3 ...`
```

However, in includes a substring which matches the "argparse.args" style (which would be cyan).

# Expected result

I expected to see `usearch -unoise3 ...` all in bold (and not cyan).

# Actual result

We do get bold, but the "-noise3" part within that is recognised as an argument, and formatted in cyan. e.g. Screenshot from prompt3 terminal:

![Screenshot 2024-12-21 at 18 30 16](https://github.com/user-attachments/assets/2e71736d-aad1-4da7-9713-eb1188398cca)

# Workaround

Make the syntax and argument styles the same (as in the commit cited).

Discussion:
@peterjc: Tested with rich==13.9.4 and rich-argparse==1.6.0 under Python 3.12 on macOS in several terminals (some of which do not show bold).@hamdanal: Yeah this looks ugly, thanks for the issue.
I don't know how to fix it though, suggestions/help welcome.@peterjc: Looking at the code, I guess it applies all of the regex matches - and thankfully there are only these two at play:

```python
_HIGHLIGHTS = [
    r"(?:^|\s)(?P<args>-{1,2}[\w]+[\w-]*)",  # highlight --words-with-dashes as args
    r"`(?P<syntax>[^`]*)`",  # highlight `text in backquotes` as syntax
]
```

And:

```python
        for highlight in self.highlights:
            rich_text.highlight_regex(highlight, style_prefix="argparse.")
```

So one fix might be some regex magic to stop matching args within backticks (beyond my regex comfort level)?

Or, in that loop applying the highlights consider the location of the matches to avoid double styling?

Or, apply the backtick detection and styling "manually" in Python code, and continue with the regex approach for the args on the text determined to be outside any backticks?

Sadly I don't see an easy fix either.@hamdanal: I think I figured out a solution in #142. If I "OR" the two regular expressions, they seam to not overlap anymore.

Could you please test it on your application before I merge it? You don't need to install anything, just add this line to your code:
```python
RichHelpFormatter.highlights = [r"`(?P<syntax>[^`]*)`|(?:^|\s)(?P<args>-{1,2}[\w]+[\w-]*)"]
```

Edit: note that adding this line to your code has the advantage that it would solve your problem today, you won't need to wait for the next release of rich-argparse.@peterjc: Perfect - that works nicely, and retains the original styling you intended. Thanks!

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbbd5f8..15cce1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 ## Unreleased
 
+### Fixes
+- [GH-141](https://github.com/hamdanal/rich-argparse/issues/141),
+  [PR-142](https://github.com/hamdanal/rich-argparse/pull/142)
+  Do not highlight --options inside backticks.
+
 ## 1.6.0 - 2024-11-02
 
 ### Fixes
diff --git a/rich_argparse/_common.py b/rich_argparse/_common.py
index 8c271f6..7f32b3e 100644
--- a/rich_argparse/_common.py
+++ b/rich_argparse/_common.py
@@ -5,9 +5,11 @@ import sys
 
 import rich_argparse._lazy_rich as r
 
+# Default highlight patterns:
+# - highlight `text in backquotes` as "syntax"
+# - --words-with-dashes outside backticks as "args"
 _HIGHLIGHTS = [
-    r"(?:^|\s)(?P<args>-{1,2}[\w]+[\w-]*)",  # highlight --words-with-dashes as args
-    r"`(?P<syntax>[^`]*)`",  # highlight `text in backquotes` as syntax
+    r"`(?P<syntax>[^`]*)`|(?:^|\s)(?P<args>-{1,2}[\w]+[\w-]*)",
 ]
 
 _windows_console_fixed = None


Test patch:
diff --git a/tests/test_argparse.py b/tests/test_argparse.py
index 9fb5f49..4a855f4 100644
--- a/tests/test_argparse.py
+++ b/tests/test_argparse.py
@@ -705,6 +705,8 @@ def test_default_highlights():
     parser.add_argument("--option-multi", action="store_true", help="Start --middle-word end")
     parser.add_argument("--option-not", action="store_true", help="Start middle-word end")
     parser.add_argument("--option-short", action="store_true", help="Start -middle end")
+    # options inside backticks should not be highlighted
+    parser.add_argument("--not-option", action="store_true", help="Start `not --option` end")
     # %(default)s highlights
     parser.add_argument("--default", default=10, help="The default value is %(default)s.")
 
@@ -723,6 +725,7 @@ def test_default_highlights():
       \x1b[36m--option-multi\x1b[0m     \x1b[39mStart \x1b[0m\x1b[36m--middle-word\x1b[0m\x1b[39m end\x1b[0m
       \x1b[36m--option-not\x1b[0m       \x1b[39mStart middle-word end\x1b[0m
       \x1b[36m--option-short\x1b[0m     \x1b[39mStart \x1b[0m\x1b[36m-middle\x1b[0m\x1b[39m end\x1b[0m
+      \x1b[36m--not-option\x1b[0m       \x1b[39mStart `\x1b[0m\x1b[1;39mnot --option\x1b[0m\x1b[39m` end\x1b[0m
       \x1b[36m--default\x1b[0m \x1b[38;5;36mDEFAULT\x1b[0m  \x1b[39mThe default value is \x1b[0m\x1b[3;39m10\x1b[0m\x1b[39m.\x1b[0m
 
     \x1b[39mEpilog with `\x1b[0m\x1b[1;39msyntax\x1b[0m\x1b[39m` and \x1b[0m\x1b[36m--options\x1b[0m\x1b[39m.\x1b[0m