← pod_r5_v1h

rich_3454

resolved RESOLVED PASS · 39 tool calls · 166 s · Textualize/rich

Task input

Fix [BUG] @ breaks highlighting in hyperlink #3327

## Type of changes

- [x] Bug fix
- [ ] New feature
- [ ] Documentation / docstrings
- [x] Tests
- [ ] Other

## Checklist

- [ ] I've run the latest [black](https://github.com/psf/black) with default args on new code.
- [ ] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
- [x] I've added tests for new code.
- [x] I accept that @willmcgugan may be pedantic in the code review.

## Description
Fixed the bug [#3327](https://github.com/Textualize/rich/issues/3327).

Changes -
1. Added '@' in rich.highlighter:ReprHighlighter url regex pattern.
2. Added a test case in test_highlighter for the url with '@'.

## Note
Poetry installed black v22.12.0 and its formatting differs a bit from the latest version v24.8.0. Hence I've formatted with the prior to keep it consistent.

Tool calls (40)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"ReprHighlighter\" rich/highlighter.py"
}
{"status": "ok", "stdout": "rich/highlighter.py:80:class ReprHighlighter(RegexHighlighter):\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "rich/highlighter.py:80:class ReprHighlighter(RegexHighlighter):\n", "stderr": "", "exit_code": 0}
2run_command
{
 "command": "sed -n '80,150p' rich/highlighter.py"
}
{"status": "ok", "stdout": "class ReprHighlighter(RegexHighlighter):\n \"\"\"Highlights the text typically produced from ``__repr__`` methods.\"\"\"\n\n base_st
full
{"status": "ok", "stdout": "class ReprHighlighter(RegexHighlighter):\n    \"\"\"Highlights the text typically produced from ``__repr__`` methods.\"\"\"\n\n    base_style = \"repr.\"\n    highlights = [\n        r\"(?P<tag_start><)(?P<tag_name>[-\\w.:|]*)(?P<tag_contents>[\\w\\W]*)(?P<tag_end>>)\",\n        r'(?P<attrib_name>[\\w_]{1,50})=(?P<attrib_value>\"?[\\w_]+\"?)?',\n        r\"(?P<brace>[][{}()])\",\n        _combine_regex(\n            r\"(?P<ipv4>[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})\",\n            r\"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})\",\n            r\"(?P<eui64>(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\\.){3}[0-9A-Fa-f]{4})\",\n            r\"(?P<eui48>(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\\.){2}[0-9A-Fa-f]{4})\",\n            r\"(?P<uuid>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})\",\n            r\"(?P<call>[\\w.]*?)\\(\",\n            r\"\\b(?P<bool_true>True)\\b|\\b(?P<bool_false>False)\\b|\\b(?P<none>None)\\b\",\n            r\"(?P<ellipsis>\\.\\.\\.)\",\n            r\"(?P<number_complex>(?<!\\w)(?:\\-?[0-9]+\\.?[0-9]*(?:e[-+]?\\d+?)?)(?:[-+](?:[0-9]+\\.?[0-9]*(?:e[-+]?\\d+)?))?j)\",\n            r\"(?P<number>(?<!\\w)\\-?[0-9]+\\.?[0-9]*(e[-+]?\\d+?)?\\b|0x[0-9a-fA-F]*)\",\n            r\"(?P<path>\\B(/[-\\w._+]+)*\\/)(?P<filename>[-\\w._+]*)?\",\n            r\"(?<![\\\\\\w])(?P<str>b?'''.*?(?<!\\\\)'''|b?'.*?(?<!\\\\)'|b?\\\"\\\"\\\".*?(?<!\\\\)\\\"\\\"\\\"|b?\\\".*?(?<!\\\\)\\\")\",\n            r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\",\n        ),\n    ]\n\n\nclass JSONHighlighter(RegexHighlighter):\n    \"\"\"Highlights JSON\"\"\"\n\n    # Captures the start and end of JSON strings, handling escaped quotes\n    JSON_STR = r\"(?<![\\\\\\w])(?P<str>b?\\\".*?(?<!\\\\)\\\")\"\n    JSON_WHITESPACE = {\" \", \"\\n\", \"\\r\", \"\\t\"}\
3run_command
{
 "command": "grep -rn \"test_highlighter\" tests/"
}
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
full
{"status": "error", "error_type": "CommandError", "error_message": "", "details": {"stdout": "", "stderr": "", "exit_code": 1}}
4run_command
{
 "command": "find tests -name \"*highlighter*\""
}
{"status": "ok", "stdout": "tests/test_highlighter.py\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "tests/test_highlighter.py\n", "stderr": "", "exit_code": 0}
5run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user:pass@example.com\")\nhighlighter.highlight(text)\nprint(f\"Text: {text.plain}\")\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Text: https://user:pass@example.com\nSpans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Text: https://user:pass@example.com\nSpans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0}
6run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\n\ntests = [\n    \"https://example.com\",\n    \"https://user:pass@example.com\",\n    \"http://example.com\",\n    \"http://user:pass@example.com\",\n    \"ws://example.com\",\n    \"wss://example.com\",\n    \"file:///tmp/test\",\n]\n\nfor t in tests:\n    text = Text(t)\n    highlighter.highlight(text)\n    print(f\"'{t}': {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "'https://example.com': [Span(0, 19, 'repr.url')]\n'https://user:pass@example.com': [Span(0, 17, 'repr.url')]\n'http://example.com':
full
{"status": "ok", "stdout": "'https://example.com': [Span(0, 19, 'repr.url')]\n'https://user:pass@example.com': [Span(0, 17, 'repr.url')]\n'http://example.com': [Span(0, 18, 'repr.url')]\n'http://user:pass@example.com': [Span(0, 16, 'repr.url')]\n'ws://example.com': [Span(0, 16, 'repr.url')]\n'wss://example.com': [Span(0, 17, 'repr.url')]\n'file:///tmp/test': [Span(0, 16, 'repr.url')]\n", "stderr": "", "exit_code": 0}
7run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"Check this: https://user:pass@example.com\")\nhighlighter.highlight(text)\nprint(f\"Text: {text.plain}\")\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Text: Check this: https://user:pass@example.com\nSpans: [Span(12, 29, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Text: Check this: https://user:pass@example.com\nSpans: [Span(12, 29, 'repr.url')]\n", "stderr": "", "exit_code": 0}
8run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user@example.com\")\nhighlighter.highlight(text)\nprint(f\"Text: {text.plain}\")\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Text: https://user@example.com\nSpans: [Span(0, 12, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Text: https://user@example.com\nSpans: [Span(0, 12, 'repr.url')]\n", "stderr": "", "exit_code": 0}
9run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\nimport re\n\nhighlighter = ReprHighlighter()\n# The current regex for url is: r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\"\n# Let's see if @ is actually missing.\nregex_str = r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\"\ntext_str = \"https://user@example.com\"\nmatch = re.search(regex_str, text_str)\nprint(f\"Match: {match}\")\nif match:\n    print(f\"Matched text: {match.group('url')}\")\nPY\n"
}
{"status": "ok", "stdout": "Match: <re.Match object; span=(0, 12), match='https://user'>\nMatched text: https://user\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Match: <re.Match object; span=(0, 12), match='https://user'>\nMatched text: https://user\n", "stderr": "", "exit_code": 0}
10run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user@example.com\")\nhighlighter.highlight(text)\nprint(f\"Text: {text.plain}\")\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Text: https://user@example.com\nSpans: [Span(0, 12, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Text: https://user@example.com\nSpans: [Span(0, 12, 'repr.url')]\n", "stderr": "", "exit_code": 0}
11run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user:pass@example.com\")\nhighlighter.highlight(text)\nprint(f\"Text: {text.plain}\")\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Text: https://user:pass@example.com\nSpans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Text: https://user:pass@example.com\nSpans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user:pass@example.com\")\nhighlighter.highlight(text)\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Spans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Spans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0}
13run_command
{
 "command": "python3 - <<'PY'\nimport re\nregex_str = r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\"\ntext_str = \"https://user:pass@example.com\"\nmatch = re.search(regex_str, text_str)\nprint(f\"Matched text: {match.group('url') if match else None}\")\nPY\n"
}
{"status": "ok", "stdout": "Matched text: https://user:pass\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Matched text: https://user:pass\n", "stderr": "", "exit_code": 0}
14-15 ×2run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user:pass@example.com\")\nhighlighter.highlight(text)\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Spans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Spans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0}
16run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\nimport re\n\n# Let's find the actual regex being used by ReprHighlighter\n# ReprHighlighter is a RegexHighlighter. Its highlights list contains the regexes.\n# Since _combine_regex is used, it's a bit complex.\n# Let's just look at the source code again.\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "sed -n '115,125p' rich/highlighter.py"
}
{"status": "ok", "stdout": " _combine_regex(\n r\"(?P<brace>[\\{\\[\\(\\)\\]\\}])\",\n r\"\\b(?P<bool_true>true)\\b|\\b(?P<bool_false>false)\\b|\\b(?P<null>null
full
{"status": "ok", "stdout": "        _combine_regex(\n            r\"(?P<brace>[\\{\\[\\(\\)\\]\\}])\",\n            r\"\\b(?P<bool_true>true)\\b|\\b(?P<bool_false>false)\\b|\\b(?P<null>null)\\b\",\n            r\"(?P<number>(?<!\\w)\\-?[0-9]+\\.?[0-9]*(e[\\-\\+]?\\d+?)?\\b|0x[0-9a-fA-F]*)\",\n            JSON_STR,\n        ),\n    ]\n\n    def highlight(self, text: Text) -> None:\n        super().highlight(text)\n\n", "stderr": "", "exit_code": 0}
18run_command
{
 "command": "sed -n '80,115p' rich/highlighter.py"
}
{"status": "ok", "stdout": "class ReprHighlighter(RegexHighlighter):\n \"\"\"Highlights the text typically produced from ``__repr__`` methods.\"\"\"\n\n base_st
full
{"status": "ok", "stdout": "class ReprHighlighter(RegexHighlighter):\n    \"\"\"Highlights the text typically produced from ``__repr__`` methods.\"\"\"\n\n    base_style = \"repr.\"\n    highlights = [\n        r\"(?P<tag_start><)(?P<tag_name>[-\\w.:|]*)(?P<tag_contents>[\\w\\W]*)(?P<tag_end>>)\",\n        r'(?P<attrib_name>[\\w_]{1,50})=(?P<attrib_value>\"?[\\w_]+\"?)?',\n        r\"(?P<brace>[][{}()])\",\n        _combine_regex(\n            r\"(?P<ipv4>[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})\",\n            r\"(?P<ipv6>([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})\",\n            r\"(?P<eui64>(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\\.){3}[0-9A-Fa-f]{4})\",\n            r\"(?P<eui48>(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\\.){2}[0-9A-Fa-f]{4})\",\n            r\"(?P<uuid>[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})\",\n            r\"(?P<call>[\\w.]*?)\\(\",\n            r\"\\b(?P<bool_true>True)\\b|\\b(?P<bool_false>False)\\b|\\b(?P<none>None)\\b\",\n            r\"(?P<ellipsis>\\.\\.\\.)\",\n            r\"(?P<number_complex>(?<!\\w)(?:\\-?[0-9]+\\.?[0-9]*(?:e[-+]?\\d+?)?)(?:[-+](?:[0-9]+\\.?[0-9]*(?:e[-+]?\\d+)?))?j)\",\n            r\"(?P<number>(?<!\\w)\\-?[0-9]+\\.?[0-9]*(e[-+]?\\d+?)?\\b|0x[0-9a-fA-F]*)\",\n            r\"(?P<path>\\B(/[-\\w._+]+)*\\/)(?P<filename>[-\\w._+]*)?\",\n            r\"(?<![\\\\\\w])(?P<str>b?'''.*?(?<!\\\\)'''|b?'.*?(?<!\\\\)'|b?\\\"\\\"\\\".*?(?<!\\\\)\\\"\\\"\\\"|b?\\\".*?(?<!\\\\)\\\")\",\n            r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\",\n        ),\n    ]\n\n\nclass JSONHighlighter(RegexHighlighter):\n    \"\"\"Highlights JSON\"\"\"\n\n    # Captures the start and end of JSON strings, handling escaped quotes\n    JSON_STR = r\"(?<![\\\\\\w])(?P<str>b?\\\".*?(?<!\\\\)\\\")\"\n    JSON_WHITESPACE = {\" \", \"\\n\", \"\\r\", \"\\t\"}\
19run_command
{
 "command": "python3 - <<'PY'\nimport re\n# The current regex for url in rich/highlighter.py\nurl_regex = r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\"\ntext_str = \"https://user@example.com\"\nmatch = re.search(url_regex, text_str)\nif match:\n    print(f\"Matched: {match.group('url')}\")\nelse:\n    print(\"No match\")\nPY\n"
}
{"status": "ok", "stdout": "Matched: https://user\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Matched: https://user\n", "stderr": "", "exit_code": 0}
20run_command
{
 "command": "python3 - <<'PY'\nimport re\n# Adding @ to the regex\nurl_regex = r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)\"\ntext_str = \"https://user@example.com\"\nmatch = re.search(url_regex, text_str)\nif match:\n    print(f\"Matched: {match.group('url')}\")\nelse:\n    print(\"No match\")\nPY\n"
}
{"status": "ok", "stdout": "Matched: https://user@example.com\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Matched: https://user@example.com\n", "stderr": "", "exit_code": 0}
21run_command
{
 "command": "python3 - <<'PY'\nimport re\nurl_regex = r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)\"\ntest_cases = [\n    \"https://user@example.com\",\n    \"https://user:pass@example.com\",\n    \"https://example.com\",\n    \"http://user@example.com\",\n    \"http://user:pass@example.com\",\n    \"http://example.com\",\n    \"ws://user@example.com\",\n    \"wss://user@example.com\",\n    \"file:///tmp/test\",\n]\nfor t in test_cases:\n    match = re.search(url_regex, t)\n    print(f\"'{t}': {match.group('url') if match else None}\")\nPY\n"
}
{"status": "ok", "stdout": "'https://user@example.com': https://user@example.com\n'https://user:pass@example.com': https://user:pass@example.com\n'https://examp
full
{"status": "ok", "stdout": "'https://user@example.com': https://user@example.com\n'https://user:pass@example.com': https://user:pass@example.com\n'https://example.com': https://example.com\n'http://user@example.com': http://user@example.com\n'http://user:pass@example.com': http://user:pass@example.com\n'http://example.com': http://example.com\n'ws://user@example.com': ws://user@example.com\n'wss://user@example.com': wss://user@example.com\n'file:///tmp/test': file:///tmp/test\n", "stderr": "", "exit_code": 0}
22run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/highlighter.py\")\ns = p.read_text()\nold = r'r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\"'\nnew = r'r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)\"'\n# Need to be careful with raw strings and how they are represented in the file\n# The file has r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\"\n# Let's use a more robust replace.\nimport re\npattern = r'r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\"'\nreplacement = r'r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)\"'\ns = s.replace(pattern, replacement)\np.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
23run_command
{
 "command": "sed -n '110,120p' rich/highlighter.py"
}
{"status": "ok", "stdout": " JSON_STR = r\"(?<![\\\\\\w])(?P<str>b?\\\".*?(?<!\\\\)\\\")\"\n JSON_WHITESPACE = {\" \", \"\\n\", \"\\r\", \"\\t\"}\n\n base_style
full
{"status": "ok", "stdout": "    JSON_STR = r\"(?<![\\\\\\w])(?P<str>b?\\\".*?(?<!\\\\)\\\")\"\n    JSON_WHITESPACE = {\" \", \"\\n\", \"\\r\", \"\\t\"}\n\n    base_style = \"json.\"\n    highlights = [\n        _combine_regex(\n            r\"(?P<brace>[\\{\\[\\(\\)\\]\\}])\",\n            r\"\\b(?P<bool_true>true)\\b|\\b(?P<bool_false>false)\\b|\\b(?P<null>null)\\b\",\n            r\"(?P<number>(?<!\\w)\\-?[0-9]+\\.?[0-9]*(e[\\-\\+]?\\d+?)?\\b|0x[0-9a-fA-F]*)\",\n            JSON_STR,\n        ),\n", "stderr": "", "exit_code": 0}
24run_command
{
 "command": "sed -n '100,115p' rich/highlighter.py"
}
{"status": "ok", "stdout": " r\"(?<![\\\\\\w])(?P<str>b?'''.*?(?<!\\\\)'''|b?'.*?(?<!\\\\)'|b?\\\"\\\"\\\".*?(?<!\\\\)\\\"\\\"\\\"|b?\\\".*?(?<!\\\\)\\\")\",\n
full
{"status": "ok", "stdout": "            r\"(?<![\\\\\\w])(?P<str>b?'''.*?(?<!\\\\)'''|b?'.*?(?<!\\\\)'|b?\\\"\\\"\\\".*?(?<!\\\\)\\\"\\\"\\\"|b?\\\".*?(?<!\\\\)\\\")\",\n            r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)\",\n        ),\n    ]\n\n\nclass JSONHighlighter(RegexHighlighter):\n    \"\"\"Highlights JSON\"\"\"\n\n    # Captures the start and end of JSON strings, handling escaped quotes\n    JSON_STR = r\"(?<![\\\\\\w])(?P<str>b?\\\".*?(?<!\\\\)\\\")\"\n    JSON_WHITESPACE = {\" \", \"\\n\", \"\\r\", \"\\t\"}\n\n    base_style = \"json.\"\n    highlights = [\n        _combine_regex(\n", "stderr": "", "exit_code": 0}
25run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user@example.com\")\nhighlighter.highlight(text)\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Spans: [Span(0, 24, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Spans: [Span(0, 24, 'repr.url')]\n", "stderr": "", "exit_code": 0}
26run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user:pass@example.com\")\nhighlighter.highlight(text)\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Spans: [Span(0, 29, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Spans: [Span(0, 29, 'repr.url')]\n", "stderr": "", "exit_code": 0}
27run_command
{
 "command": "cat tests/test_highlighter.py | head -n 100"
}
{"status": "ok", "stdout": "\"\"\"Tests for the highlighter classes.\"\"\"\nimport json\nfrom typing import List\n\nimport pytest\n\nfrom rich.highlighter impor
full
{"status": "ok", "stdout": "\"\"\"Tests for the highlighter classes.\"\"\"\nimport json\nfrom typing import List\n\nimport pytest\n\nfrom rich.highlighter import (\n    ISO8601Highlighter,\n    JSONHighlighter,\n    NullHighlighter,\n    ReprHighlighter,\n)\nfrom rich.text import Span, Text\n\n\ndef test_wrong_type():\n    highlighter = NullHighlighter()\n    with pytest.raises(TypeError):\n        highlighter([])\n\n\nhighlight_tests = [\n    (\"\", []),\n    (\" \", []),\n    (\n        \"<foo>\",\n        [\n            Span(0, 1, \"repr.tag_start\"),\n            Span(1, 4, \"repr.tag_name\"),\n            Span(4, 5, \"repr.tag_end\"),\n        ],\n    ),\n    (\n        \"<foo: 23>\",\n        [\n            Span(0, 1, \"repr.tag_start\"),\n            Span(1, 5, \"repr.tag_name\"),\n            Span(5, 8, \"repr.tag_contents\"),\n            Span(8, 9, \"repr.tag_end\"),\n            Span(6, 8, \"repr.number\"),\n        ],\n    ),\n    (\n        \"<foo: <bar: 23>>\",\n        [\n            Span(0, 1, \"repr.tag_start\"),\n            Span(1, 5, \"repr.tag_name\"),\n            Span(5, 15, \"repr.tag_contents\"),\n            Span(15, 16, \"repr.tag_end\"),\n            Span(12, 14, \"repr.number\"),\n        ],\n    ),\n    (\n        \"False True None\",\n        [\n            Span(0, 5, \"repr.bool_false\"),\n            Span(6, 10, \"repr.bool_true\"),\n            Span(11, 15, \"repr.none\"),\n        ],\n    ),\n    (\"foo=bar\", [Span(0, 3, \"repr.attrib_name\"), Span(4, 7, \"repr.attrib_value\")]),\n    (\n        'foo=\"bar\"',\n        [\n            Span(0, 3, \"repr.attrib_name\"),\n            Span(4, 9, \"repr.attrib_value\"),\n            Span(4, 9, \"repr.str\"),\n        ],\n    ),\n    (\n        \"<Permission.WRITE|READ: 3>\",\n        [\n            Span(0, 1, \"repr.tag_start\"),\n            Span(1, 23, \"repr.tag_name\"),\n            Span(23, 25, \"repr.tag_contents\"),\n            Span(25, 26, \"repr.tag_end\"),\n            Span(2
28run_command
{
 "command": "grep -n \"highlight_tests =\" tests/test_highlighter.py"
}
{"status": "ok", "stdout": "22:highlight_tests = [\n219:iso8601_highlight_tests = [\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "22:highlight_tests = [\n219:iso8601_highlight_tests = [\n", "stderr": "", "exit_code": 0}
29run_command
{
 "command": "sed -n '150,200p' tests/test_highlighter.py"
}
{"status": "ok", "stdout": " ('\"\"', [Span(0, 2, \"repr.str\")]),\n ('\"hello\"', [Span(0, 7, \"repr.str\")]),\n ('\"\"\"hello\"\"\"', [Span(0, 11, \"repr.str\
full
{"status": "ok", "stdout": "    ('\"\"', [Span(0, 2, \"repr.str\")]),\n    ('\"hello\"', [Span(0, 7, \"repr.str\")]),\n    ('\"\"\"hello\"\"\"', [Span(0, 11, \"repr.str\")]),\n    (\"\\\\'foo'\", []),\n    (\"it's no 'string'\", [Span(8, 16, \"repr.str\")]),\n    (\"78351748-9b32-4e08-ad3e-7e9ff124d541\", [Span(0, 36, \"repr.uuid\")]),\n]\n\n\n@pytest.mark.parametrize(\"test, spans\", highlight_tests)\ndef test_highlight_regex(test: str, spans: List[Span]):\n    \"\"\"Tests for the regular expressions used in ReprHighlighter.\"\"\"\n    text = Text(test)\n    highlighter = ReprHighlighter()\n    highlighter.highlight(text)\n    print(text.spans)\n    assert text.spans == spans\n\n\ndef test_highlight_json_with_indent():\n    json_string = json.dumps({\"name\": \"apple\", \"count\": 1}, indent=4)\n    text = Text(json_string)\n    highlighter = JSONHighlighter()\n    highlighter.highlight(text)\n    assert text.spans == [\n        Span(0, 1, \"json.brace\"),\n        Span(6, 12, \"json.str\"),\n        Span(14, 21, \"json.str\"),\n        Span(27, 34, \"json.str\"),\n        Span(36, 37, \"json.number\"),\n        Span(38, 39, \"json.brace\"),\n        Span(6, 12, \"json.key\"),\n        Span(27, 34, \"json.key\"),\n    ]\n\n\ndef test_highlight_json_string_only():\n    json_string = '\"abc\"'\n    text = Text(json_string)\n    highlighter = JSONHighlighter()\n    highlighter.highlight(text)\n    assert text.spans == [Span(0, 5, \"json.str\")]\n\n\ndef test_highlight_json_empty_string_only():\n    json_string = '\"\"'\n    text = Text(json_string)\n    highlighter = JSONHighlighter()\n    highlighter.highlight(text)\n    assert text.spans == [Span(0, 2, \"json.str\")]\n\n", "stderr": "", "exit_code": 0}
30run_command
{
 "command": "sed -n '100,150p' tests/test_highlighter.py"
}
{"status": "ok", "stdout": " Span(1, 2, \"repr.brace\"),\n Span(6, 7, \"repr.brace\"),\n Span(2, 6, \"repr.number_complex\"),\n ],\n ),\n (\n \" (123456.4321-12
full
{"status": "ok", "stdout": "            Span(1, 2, \"repr.brace\"),\n            Span(6, 7, \"repr.brace\"),\n            Span(2, 6, \"repr.number_complex\"),\n        ],\n    ),\n    (\n        \" (123456.4321-1234.5678j) \",\n        [\n            Span(1, 2, \"repr.brace\"),\n            Span(24, 25, \"repr.brace\"),\n            Span(2, 24, \"repr.number_complex\"),\n        ],\n    ),\n    (\n        \" (-123123-2.1312342342423422e+25j) \",\n        [\n            Span(1, 2, \"repr.brace\"),\n            Span(33, 34, \"repr.brace\"),\n            Span(2, 33, \"repr.number_complex\"),\n        ],\n    ),\n    (\" /foo \", [Span(1, 2, \"repr.path\"), Span(2, 5, \"repr.filename\")]),\n    (\" /foo/bar.html \", [Span(1, 6, \"repr.path\"), Span(6, 14, \"repr.filename\")]),\n    (\"01-23-45-67-89-AB\", [Span(0, 17, \"repr.eui48\")]),  # 6x2 hyphen\n    (\"01-23-45-FF-FE-67-89-AB\", [Span(0, 23, \"repr.eui64\")]),  # 8x2 hyphen\n    (\"01:23:45:67:89:AB\", [Span(0, 17, \"repr.ipv6\")]),  # 6x2 colon\n    (\"01:23:45:FF:FE:67:89:AB\", [Span(0, 23, \"repr.ipv6\")]),  # 8x2 colon\n    (\"0123.4567.89AB\", [Span(0, 14, \"repr.eui48\")]),  # 3x4 dot\n    (\"0123.45FF.FE67.89AB\", [Span(0, 19, \"repr.eui64\")]),  # 4x4 dot\n    (\"ed-ed-ed-ed-ed-ed\", [Span(0, 17, \"repr.eui48\")]),  # lowercase\n    (\"ED-ED-ED-ED-ED-ED\", [Span(0, 17, \"repr.eui48\")]),  # uppercase\n    (\"Ed-Ed-Ed-Ed-Ed-Ed\", [Span(0, 17, \"repr.eui48\")]),  # mixed case\n    (\"0-00-1-01-2-02\", [Span(0, 14, \"repr.eui48\")]),  # dropped zero\n    (\" https://example.org \", [Span(1, 20, \"repr.url\")]),\n    (\" http://example.org \", [Span(1, 19, \"repr.url\")]),\n    (\" http://example.org/index.html \", [Span(1, 30, \"repr.url\")]),\n    (\" http://example.org/index.html#anchor \", [Span(1, 37, \"repr.url\")]),\n    (\n        \" http://example.org/index.html?param1=value1 \",\n        [\n            Span(31, 37, \"repr.attrib_name\"),\n            Span(38, 44, \"repr.attrib_value\"),\n            
31run_command
{
 "command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"tests/test_highlighter.py\")\ns = p.read_text()\n# Find where highlight_tests = [ is\nimport re\nmatch = re.search(r'highlight_tests = \\[', s)\nif match:\n    start = match.start()\n    # We want to add a test case. Let's find the end of the list.\n    # The list ends before @pytest.mark.parametrize\n    end = s.find(']', start)\n    # Actually, it's better to find the last element of the list before the end.\n    # Let's just insert it before the last element if it's a simple list.\n    # Or better, just append it to the list.\n    \n    # find the end of the list by looking for the closing bracket that is followed by newline and then newline and then @pytest\n    # Or just find the last ')' before ']'\n    \n    # Looking at sed output, it ends with:\n    #     ('\"\"', [Span(0, 2, \"repr.str\")]),\n    # ]\n    \n    last_element_end = s.rfind('),', 0, end)\n    if last_element_end != -1:\n        insertion_point = last_element_end + 2\n        new_element = '    (\"https://user@example.com\", [Span(0, 24, \"repr.url\")]),\\n'\n        s = s[:insertion_point] + new_element + s[insertion_point:]\n        p.write_text(s)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
32run_command
{
 "command": "python3 -m pytest tests/test_highlighter.py -q -k test_highlight_regex"
}
{"status": "ok", "stdout": ".................................................. [100%]\n50 passed, 32 deselected in 0.08s\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "..................................................                       [100%]\n50 passed, 32 deselected in 0.08s\n", "stderr": "", "exit_code": 0}
33run_command
{
 "command": "git diff"
}
{"status": "ok", "stdout": "diff --git a/rich/highlighter.py b/rich/highlighter.py\nindex 27714b25..e4c462e2 100644\n--- a/rich/highlighter.py\n+++ b/rich/highl
full
{"status": "ok", "stdout": "diff --git a/rich/highlighter.py b/rich/highlighter.py\nindex 27714b25..e4c462e2 100644\n--- a/rich/highlighter.py\n+++ b/rich/highlighter.py\n@@ -98,7 +98,7 @@ class ReprHighlighter(RegexHighlighter):\n             r\"(?P<number>(?<!\\w)\\-?[0-9]+\\.?[0-9]*(e[-+]?\\d+?)?\\b|0x[0-9a-fA-F]*)\",\n             r\"(?P<path>\\B(/[-\\w._+]+)*\\/)(?P<filename>[-\\w._+]*)?\",\n             r\"(?<![\\\\\\w])(?P<str>b?'''.*?(?<!\\\\)'''|b?'.*?(?<!\\\\)'|b?\\\"\\\"\\\".*?(?<!\\\\)\\\"\\\"\\\"|b?\\\".*?(?<!\\\\)\\\")\",\n-            r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\",\n+            r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)\",\n         ),\n     ]\n \n", "stderr": "", "exit_code": 0}
34run_command
{
 "command": "git status"
}
{"status": "ok", "stdout": "On branch main\nChanges not staged for commit:\n (use \"git add <file>...\" to update what will be committed)\n (use \"git restore <
full
{"status": "ok", "stdout": "On branch main\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git restore <file>...\" to discard changes in working directory)\n\tmodified:   rich/highlighter.py\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n", "stderr": "", "exit_code": 0}
35run_command
{
 "command": "git restore tests/test_highlighter.py"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
36run_command
{
 "command": "git diff"
}
{"status": "ok", "stdout": "diff --git a/rich/highlighter.py b/rich/highlighter.py\nindex 27714b25..e4c462e2 100644\n--- a/rich/highlighter.py\n+++ b/rich/highl
full
{"status": "ok", "stdout": "diff --git a/rich/highlighter.py b/rich/highlighter.py\nindex 27714b25..e4c462e2 100644\n--- a/rich/highlighter.py\n+++ b/rich/highlighter.py\n@@ -98,7 +98,7 @@ class ReprHighlighter(RegexHighlighter):\n             r\"(?P<number>(?<!\\w)\\-?[0-9]+\\.?[0-9]*(e[-+]?\\d+?)?\\b|0x[0-9a-fA-F]*)\",\n             r\"(?P<path>\\B(/[-\\w._+]+)*\\/)(?P<filename>[-\\w._+]*)?\",\n             r\"(?<![\\\\\\w])(?P<str>b?'''.*?(?<!\\\\)'''|b?'.*?(?<!\\\\)'|b?\\\"\\\"\\\".*?(?<!\\\\)\\\"\\\"\\\"|b?\\\".*?(?<!\\\\)\\\")\",\n-            r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)\",\n+            r\"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)\",\n         ),\n     ]\n \n", "stderr": "", "exit_code": 0}
37run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user@example.com\")\nhighlighter.highlight(text)\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Spans: [Span(0, 24, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Spans: [Span(0, 24, 'repr.url')]\n", "stderr": "", "exit_code": 0}
38run_command
{
 "command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntext = Text(\"https://user:pass@example.com\")\nhighlighter.highlight(text)\nprint(f\"Spans: {text.spans}\")\nPY\n"
}
{"status": "ok", "stdout": "Spans: [Span(0, 29, 'repr.url')]\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Spans: [Span(0, 29, 'repr.url')]\n", "stderr": "", "exit_code": 0}
39run_command
{
 "command": "submit_patch"
}
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bas
full
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}}
40submit_patch
{}
{"status": "ok", "patch_size": 657, "files_changed": 1}
full
{"status": "ok", "patch_size": 657, "files_changed": 1}

Patch

diff --git a/rich/highlighter.py b/rich/highlighter.py
index 27714b25..e4c462e2 100644
--- a/rich/highlighter.py
+++ b/rich/highlighter.py
@@ -98,7 +98,7 @@ class ReprHighlighter(RegexHighlighter):
             r"(?P<number>(?<!\w)\-?[0-9]+\.?[0-9]*(e[-+]?\d+?)?\b|0x[0-9a-fA-F]*)",
             r"(?P<path>\B(/[-\w._+]+)*\/)(?P<filename>[-\w._+]*)?",
             r"(?<![\\\w])(?P<str>b?'''.*?(?<!\\)'''|b?'.*?(?<!\\)'|b?\"\"\".*?(?<!\\)\"\"\"|b?\".*?(?<!\\)\")",
-            r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~]*)",
+            r"(?P<url>(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#~@]*)",
         ),
     ]
 

Test output

show
........................................................................ [ 86%]
...........                                                              [100%]
83 passed in 0.09s