resolved RESOLVED PASS · 14 tool calls · 48 s · Textualize/rich
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 | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn 'ReprHighlighter' rich/highlighter.py || echo 'NOT FOUND'"
} | {"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} |
| 2 | run_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_stfull{"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\"}\ |
| 3 | run_command | {
"command": "grep -rn 'test_highlighter' . || echo 'NOT FOUND'"
} | {"status": "ok", "stdout": "Binary file ./.git/index matches\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Binary file ./.git/index matches\n", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "find . -name '*test_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} |
| 5 | run_command | {
"command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntexts = [\n \"https://example.com\",\n \"https://user:pass@example.com\",\n]\n\nfor t_str in texts:\n text = Text(t_str)\n highlighter.highlight(text)\n print(f\"Text: {t_str}, Spans: {text.spans}\")\nPY\n"
} | {"status": "ok", "stdout": "Text: https://example.com, Spans: [Span(0, 19, 'repr.url')]\nText: https://user:pass@example.com, Spans: [Span(0, 17, 'repr.url')]\nfull{"status": "ok", "stdout": "Text: https://example.com, Spans: [Span(0, 19, 'repr.url')]\nText: https://user:pass@example.com, Spans: [Span(0, 17, 'repr.url')]\n", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"rich/highlighter.py\"); s = 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$_+!`(),.?/;:&=%#@~]*)\",'\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new); p.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "python3 - <<'PY'\nfrom rich.highlighter import ReprHighlighter\nfrom rich.text import Text\n\nhighlighter = ReprHighlighter()\ntexts = [\n \"https://example.com\",\n \"https://user:pass@example.com\",\n]\n\nfor t_str in texts:\n text = Text(t_str)\n highlighter.highlight(text)\n print(f\"Text: {t_str}, Spans: {text.spans}\")\nPY\n"
} | {"status": "ok", "stdout": "Text: https://example.com, Spans: [Span(0, 19, 'repr.url')]\nText: https://user:pass@example.com, Spans: [Span(0, 29, 'repr.url')]\nfull{"status": "ok", "stdout": "Text: https://example.com, Spans: [Span(0, 19, 'repr.url')]\nText: https://user:pass@example.com, Spans: [Span(0, 29, 'repr.url')]\n", "stderr": "", "exit_code": 0} |
| 8 | run_command | {
"command": "sed -n '1,100p' tests/test_highlighter.py"
} | {"status": "ok", "stdout": "\"\"\"Tests for the highlighter classes.\"\"\"\nimport json\nfrom typing import List\n\nimport pytest\n\nfrom rich.highlighter imporfull{"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 |
| 9 | run_command | {
"command": "grep -n \"repr.url\" tests/test_highlighter.py || echo \"NOT FOUND\""
} | {"status": "ok", "stdout": "133: (\" https://example.org \", [Span(1, 20, \"repr.url\")]),\n134: (\" http://example.org \", [Span(1, 19, \"repr.url\")]),\n135: full{"status": "ok", "stdout": "133: (\" https://example.org \", [Span(1, 20, \"repr.url\")]),\n134: (\" http://example.org \", [Span(1, 19, \"repr.url\")]),\n135: (\" http://example.org/index.html \", [Span(1, 30, \"repr.url\")]),\n136: (\" http://example.org/index.html#anchor \", [Span(1, 37, \"repr.url\")]),\n142: Span(1, 44, \"repr.url\"),\n145: (\" http://example.org/~folder \", [Span(1, 27, \"repr.url\")]),\n", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "sed -n '120,150p' tests/test_highlighter.py"
} | {"status": "ok", "stdout": " ),\n (\" /foo \", [Span(1, 2, \"repr.path\"), Span(2, 5, \"repr.filename\")]),\n (\" /foo/bar.html \", [Span(1, 6, \"repr.path\"), full{"status": "ok", "stdout": " ),\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 Span(1, 44, \"repr.url\"),\n ],\n ),\n (\" http://example.org/~folder \", [Span(1, 27, \"repr.url\")]),\n (\"No place like 127.0.0.1\", [Span(14, 23, \"repr.ipv4\")]),\n (\"''\", [Span(0, 2, \"repr.str\")]),\n (\"'hello'\", [Span(0, 7, \"repr.str\")]),\n (\"'''hello'''\", [Span(0, 11, \"repr.str\")]),\n ('\"\"', [Span(0, 2, \"repr.str\")]),\n", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"tests/test_highlighter.py\"); s = p.read_text()\nold = r' (\" http://example.org/~folder \", [Span(1, 27, \"repr.url\")]),'\nnew = r' (\" http://example.org/~folder \", [Span(1, 27, \"repr.url\")]),' + '\\n' + r' (\" https://user:pass@example.com \", [Span(1, 30, \"repr.url\")]),'\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new); p.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "python3 -m pytest tests/test_highlighter.py -q"
} | {"status": "ok", "stdout": "........................................................................ [ 86%]\n........... [100%]\n83 passed in 0.09s\n", "stderr"full{"status": "ok", "stdout": "........................................................................ [ 86%]\n........... [100%]\n83 passed in 0.09s\n", "stderr": "", "exit_code": 0} |
| 13 | run_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\tmodified: tests/test_highlighter.py\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n", "stderr": "", "exit_code": 0} |
| 14 | run_command | {
"command": "git diff"
} | {"status": "ok", "stdout": "diff --git a/rich/highlighter.py b/rich/highlighter.py\nindex 27714b25..868fedef 100644\n--- a/rich/highlighter.py\n+++ b/rich/highlfull{"status": "ok", "stdout": "diff --git a/rich/highlighter.py b/rich/highlighter.py\nindex 27714b25..868fedef 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 \ndiff --git a/tests/test_highlighter.py b/tests/test_highlighter.py\nindex 30851cf7..8cca423d 100644\n--- a/tests/test_highlighter.py\n+++ b/tests/test_highlighter.py\n@@ -143,6 +143,7 @@ highlight_tests = [\n ],\n ),\n (\" http://example.org/~folder \", [Span(1, 27, \"repr.url\")]),\n+ (\" https://user:pass@example.com \", [Span(1, 30, \"repr.url\")]),\n (\"No place like 127.0.0.1\", [Span(14, 23, \"repr.ipv4\")]),\n (\"''\", [Span(0, 2, \"repr.str\")]),\n (\"'hello'\", [Span(0, 7, \"repr.str\")]),\n", "stderr": "", "exit_code": 0} |
| 15 | submit_patch | {} | {"status": "ok", "patch_size": 1161, "files_changed": 2}full{"status": "ok", "patch_size": 1161, "files_changed": 2} |
diff --git a/rich/highlighter.py b/rich/highlighter.py
index 27714b25..868fedef 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$_+!`(),.?/;:&=%#@~]*)",
),
]
diff --git a/tests/test_highlighter.py b/tests/test_highlighter.py
index 30851cf7..8cca423d 100644
--- a/tests/test_highlighter.py
+++ b/tests/test_highlighter.py
@@ -143,6 +143,7 @@ highlight_tests = [
],
),
(" http://example.org/~folder ", [Span(1, 27, "repr.url")]),
+ (" https://user:pass@example.com ", [Span(1, 30, "repr.url")]),
("No place like 127.0.0.1", [Span(14, 23, "repr.ipv4")]),
("''", [Span(0, 2, "repr.str")]),
("'hello'", [Span(0, 7, "repr.str")]),
........................................................................ [ 86%] ........... [100%] 83 passed in 0.10s