failed WRONG_FIX wrong_fix · 9 tool calls · 46 s · Textualize/rich
Fix ZWJ and edge cases Fixes https://github.com/Textualize/rich/issues/3947 Fixes https://github.com/Textualize/rich/issues/3950
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"\\\\u200d\" ."
} | {"status": "ok", "stdout": "./tests/test_cells.py:23: (\"\\u200d\", 0),\n./tests/test_cells.py:75:FM = \"\ud83d\udc69\\u200d\ud83d\udd27\"\n./tests/test_cells.pfull{"status": "ok", "stdout": "./tests/test_cells.py:23: (\"\\u200d\", 0),\n./tests/test_cells.py:75:FM = \"\ud83d\udc69\\u200d\ud83d\udd27\"\n./tests/test_cells.py:164: \"\ud83d\udc69\\u200d\ud83d\udd27\",\n./tests/test_cells.py:168: (\"a\ud83d\udc69\\u200d\ud83d\udd27\", [(0, 1, 1), (1, 4, 2)], 3),\n./tests/test_cells.py:169: (\"a\ud83d\udc69\\u200d\ud83d\udd27b\", [(0, 1, 1), (1, 4, 2), (4, 5, 1)], 4),\n./tests/test_text.py:529: female_mechanic = \"\ud83d\udc69\\u200d\ud83d\udd27\"\n./tests/test_text.py:620: female_mechanic = \"\ud83d\udc69\\u200d\ud83d\udd27\"\n./rich/_emoji_codes.py:425: \"bald_man\": \"\ud83d\udc68\\u200d\ud83e\uddb2\",\n./rich/_emoji_codes.py:426: \"bald_woman\": \"\ud83d\udc69\\u200d\ud83e\uddb2\",\n./rich/_emoji_codes.py:474: \"blond-haired_man\": \"\ud83d\udc71\\u200d\u2642\ufe0f\",\n./rich/_emoji_codes.py:475: \"blond-haired_man_dark_skin_tone\": \"\ud83d\udc71\ud83c\udfff\\u200d\u2642\ufe0f\",\n./rich/_emoji_codes.py:476: \"blond-haired_man_light_skin_tone\": \"\ud83d\udc71\ud83c\udffb\\u200d\u2642\ufe0f\",\n./rich/_emoji_codes.py:477: \"blond-haired_man_medium-dark_skin_tone\": \"\ud83d\udc71\ud83c\udffe\\u200d\u2642\ufe0f\",\n./rich/_emoji_codes.py:478: \"blond-haired_man_medium-light_skin_tone\": \"\ud83d\udc71\ud83c\udffc\\u200d\u2642\ufe0f\",\n./rich/_emoji_codes.py:479: \"blond-haired_man_medium_skin_tone\": \"\ud83d\udc71\ud83c\udffd\\u200d\u2642\ufe0f\",\n./rich/_emoji_codes.py:486: \"blond-haired_woman\": \"\ud83d\udc71\\u200d\u2640\ufe0f\",\n./rich/_emoji_codes.py:487: \"blond-haired_woman_dark_skin_tone\": \"\ud83d\udc71\ud83c\udfff\\u200d\u2640\ufe0f\",\n./rich/_emoji_codes.py:488: \"blond-haired_woman_light_skin_tone\": \"\ud83d\udc71\ud83c\udffb\\u200d\u2640\ufe0f\",\n./rich/_emoji_codes.py:489: \"blond-haired_woman_medium-dark_skin_tone\": \"\ud83d\udc71\ud83c\udffe\\u200d\u2640\ufe0f\",\n./rich/_emoji_codes.py:490: \"blond-haired_woman_medium-light_ |
| 2 | run_command | {
"command": "grep -rn \"200d\" ."
} | {"status": "ok", "stdout": "./benchmarks/results/darrenburns-2022-mbp/666d0cf2-virtualenv-py3.10.json:1:{\"commit_hash\": \"666d0cf2b2eca98ff75bd260f91b200d0bb8full{"status": "ok", "stdout": "./benchmarks/results/darrenburns-2022-mbp/666d0cf2-virtualenv-py3.10.json:1:{\"commit_hash\": \"666d0cf2b2eca98ff75bd260f91b200d0bb82e90\", \"env_name\": \"virtualenv-py3.10\", \"date\": 1637405513000, \"params\": {\"arch\": \"arm64\", \"cpu\": \"Apple M1 Pro\", \"machine\": \"darrenburns-2022-mbp\", \"num_cpu\": \"10\", \"os\": \"Darwin 21.2.0\", \"ram\": \"17179869184\", \"python\": \"3.10\"}, \"python\": \"3.10\", \"requirements\": {}, \"env_vars\": {}, \"result_columns\": [\"result\", \"params\", \"version\", \"started_at\", \"duration\", \"stats_ci_99_a\", \"stats_ci_99_b\", \"stats_q_25\", \"stats_q_75\", \"stats_number\", \"stats_repeat\", \"samples\", \"profile\"], \"results\": {\"benchmarks.ColorSuite.time_downgrade_to_eight_bit\": [[1.7059216979300207e-07], [], \"019b972f782c6291df9a6a4dea9bb3c6268a69b177e71383d6e19a608e84920d\", 1646929483869, 0.46535, [1.6993e-07], [1.7094e-07], [1.7045e-07], [1.7081e-07], [64392], [10]], \"benchmarks.ColorSuite.time_downgrade_to_standard\": [[1.709019096843553e-07], [], \"f5e8556491c977450fff6ebfd9fff4fc8f12d74829aa5b8570bd27ea7a1c9d28\", 1646929484100, 0.46715, [1.69e-07], [1.717e-07], [1.7019e-07], [1.7136e-07], [64405], [10]], \"benchmarks.ColorSuite.time_downgrade_to_windows\": [[1.7002134261694357e-07], [], \"373eb97dd7ce2823e080972249235f528e8b3a47210911981cf432ecf0eef5fb\", 1646929484334, 0.46378, [1.6929e-07], [1.7051e-07], [1.6954e-07], [1.7017e-07], [64543], [10]], \"benchmarks.ColorSuiteCached.time_downgrade_to_eight_bit\": [[1.7039360575301497e-07], [], \"716ff06a471a5d90af3730f6d4470eba3577432d781e0210dff1fd156b8c549c\", 1646929484568, 0.45849, [1.6915e-07], [1.7143e-07], [1.6969e-07], [1.7058e-07], [63401], [10]], \"benchmarks.ColorSuiteCached.time_downgrade_to_standard\": [[1.7012746920399016e-07], [], \"3a752a7884e99e095c31b2a28cfefdf916cb2033c06afae1ea146a72f3ba650c\", 1646929484797, 0.46463, [1.6888e-07], [1.7061e-07], [1.6962e-07], [1.7021e-07], [64490], [10]], \"benchmarks |
| 3 | run_command | {
"command": "grep -rn \"cell_width\" ."
} | {"status": "ok", "stdout": "./tests/test_cells.py:10: _is_single_cell_widths,\n./tests/test_cells.py:137:def test_is_single_cell_widths() -> None:\n./tests/testfull{"status": "ok", "stdout": "./tests/test_cells.py:10: _is_single_cell_widths,\n./tests/test_cells.py:137:def test_is_single_cell_widths() -> None:\n./tests/test_cells.py:138: # Check _is_single_cell_widths reports correctly\n./tests/test_cells.py:141: assert _is_single_cell_widths(character)\n./tests/test_cells.py:146: assert _is_single_cell_widths(character)\n./tests/test_cells.py:149: assert not _is_single_cell_widths(character)\n./tests/test_cells.py:152: assert not _is_single_cell_widths(character)\n./rich/cells.py:35:_is_single_cell_widths: Callable[[str], bool] = _SINGLE_CELLS.issuperset\n./rich/cells.py:121: if _is_single_cell_widths(text):\n./rich/cells.py:267: if _is_single_cell_widths(text):\n./rich/cells.py:283: if _is_single_cell_widths(text):\n./rich/cells.py:310: if _is_single_cell_widths(text):\n./rich/segment.py:20: _is_single_cell_widths,\n./rich/segment.py:173: if _is_single_cell_widths(text):\n", "exit_code": 0} |
| 4 | read_file | {
"filepath": "rich/cells.py",
"start_line": 1,
"end_line": 130
} | {"status": "ok", "filepath": "rich/cells.py", "content": "from __future__ import annotations\n\nfrom functools import lru_cache\nfrom operator import itemgetterfull{"status": "ok", "filepath": "rich/cells.py", "content": "from __future__ import annotations\n\nfrom functools import lru_cache\nfrom operator import itemgetter\nfrom typing import Callable, NamedTuple, Sequence, Tuple\n\nfrom rich._unicode_data import load as load_cell_table\n\nCellSpan = Tuple[int, int, int]\n\n_span_get_cell_len = itemgetter(2)\n\n# Ranges of unicode ordinals that produce a 1-cell wide character\n# This is non-exhaustive, but covers most common Western characters\n_SINGLE_CELL_UNICODE_RANGES: list[tuple[int, int]] = [\n (0x20, 0x7E), # Latin (excluding non-printable)\n (0xA0, 0xAC),\n (0xAE, 0x002FF),\n (0x00370, 0x00482), # Greek / Cyrillic\n (0x02500, 0x025FC), # Box drawing, box elements, geometric shapes\n (0x02800, 0x028FF), # Braille\n]\n\n# A frozen set of characters that are a single cell wide\n_SINGLE_CELLS = frozenset(\n [\n character\n for _start, _end in _SINGLE_CELL_UNICODE_RANGES\n for character in map(chr, range(_start, _end + 1))\n ]\n)\n\n# When called with a string this will return True if all\n# characters are single-cell, otherwise False\n_is_single_cell_widths: Callable[[str], bool] = _SINGLE_CELLS.issuperset\n\n\nclass CellTable(NamedTuple):\n \"\"\"Contains unicode data required to measure the cell widths of glyphs.\"\"\"\n\n unicode_version: str\n widths: Sequence[tuple[int, int, int]]\n narrow_to_wide: frozenset[str]\n\n\n@lru_cache(maxsize=4096)\ndef get_character_cell_size(character: str, unicode_version: str = \"auto\") -> int:\n \"\"\"Get the cell size of a character.\n\n Args:\n character (str): A single character.\n unicode_version: Unicode version, `\"auto\"` to auto detect, `\"latest\"` for the latest unicode version.\n\n Returns:\n int: Number of cells (0, 1 or 2) occupied by that character.\n \"\"\"\n codepoint = ord(character)\n table = load_cell_table(unicode_version).widths\n if codepoint > table[-1][1]:\n |
| 5 | read_file | {
"end_line": 160,
"filepath": "rich/cells.py",
"start_line": 131
} | {"status": "ok", "filepath": "rich/cells.py", "content": "\n cell_table = load_cell_table(unicode_version)\n total_width = 0\n last_measured_character: str | Nofull{"status": "ok", "filepath": "rich/cells.py", "content": "\n cell_table = load_cell_table(unicode_version)\n total_width = 0\n last_measured_character: str | None = None\n\n SPECIAL = {\"\\u200d\", \"\\ufe0f\"}\n\n iter_characters = iter(text)\n\n for character in iter_characters:\n if character in SPECIAL:\n if character == \"\\u200d\":\n next(iter_characters)\n elif last_measured_character:\n total_width += last_measured_character in cell_table.narrow_to_wide\n last_measured_character = None\n else:\n if character_width := get_character_cell_size(character, unicode_version):\n last_measured_character = character\n total_width += character_width\n\n return total_width\n\n\ndef split_graphemes(\n text: str, unicode_version: str = \"auto\"\n) -> \"tuple[list[CellSpan], int]\":\n \"\"\"Divide text into spans that define a single grapheme.\n\n Args:\n", "start_line": 131, "end_line": 160, "total_lines": 325, "is_truncated": false} |
| 6 | read_file | {
"filepath": "rich/cells.py",
"start_line": 130,
"end_line": 150
} | {"status": "ok", "filepath": "rich/cells.py", "content": " )\n\n cell_table = load_cell_table(unicode_version)\n total_width = 0\n last_measured_character: str full{"status": "ok", "filepath": "rich/cells.py", "content": " )\n\n cell_table = load_cell_table(unicode_version)\n total_width = 0\n last_measured_character: str | None = None\n\n SPECIAL = {\"\\u200d\", \"\\ufe0f\"}\n\n iter_characters = iter(text)\n\n for character in iter_characters:\n if character in SPECIAL:\n if character == \"\\u200d\":\n next(iter_characters)\n elif last_measured_character:\n total_width += last_measured_character in cell_table.narrow_to_wide\n last_measured_character = None\n else:\n if character_width := get_character_cell_size(character, unicode_version):\n last_measured_character = character\n total_width += character_width\n", "start_line": 130, "end_line": 150, "total_lines": 325, "is_truncated": false} |
| 7 | edit_file | {
"filepath": "rich/cells.py",
"new_string": " if character == \"\\u200d\":\n pass",
"old_string": " if character == \"\\u200d\":\n next(iter_characters)"
} | {"status": "ok", "filepath": "rich/cells.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/rich/cells.py\n+++ b/rich/cells.py\n@@ -140,7 +140,7 @@\n fofull{"status": "ok", "filepath": "rich/cells.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/rich/cells.py\n+++ b/rich/cells.py\n@@ -140,7 +140,7 @@\n for character in iter_characters:\n if character in SPECIAL:\n if character == \"\\u200d\":\n- next(iter_characters)\n+ pass\n elif last_measured_character:\n total_width += last_measured_character in cell_table.narrow_to_wide\n last_measured_character = None\n", "is_truncated": false} |
| 8 | run_command | {
"command": "python3 -c \"from rich.cells import _cell_len; print(f'ZWJ test: {_cell_len(\\\"A\\u200dB\\\")}'); print(f'ZWJ-ZWJ test: {_cell_len(\\\"A\\u200d\\u200dB\\\")}')\""
} | {"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recenfull{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n from rich.cells import _cell_len; print(f'ZWJ test: {_cell_len(\"A\\u200dB\")}'); print(f'ZWJ-ZWJ test: {_cell_len(\"A\\u200d\\u200dB\")}')\n ~~~~~~~~~^^^^^^^^^^^^\nTypeError: _cell_len() missing 1 required positional argument: 'unicode_version'\n", "exit_code": 1}} |
| 9 | run_command | {
"command": "python3 -c \"from rich.cells import _cell_len; print(f'ZWJ test: {_cell_len(\\\"A\\u200dB\\\", \\\"15.0\\\")}'); print(f'ZWJ-ZWJ test: {_cell_len(\\\"A\\u200d\\u200dB\\\", \\\"15.0\\\")}')\""
} | {"status": "ok", "stdout": "ZWJ test: 2\nZWJ-ZWJ test: 2\n", "exit_code": 0}full{"status": "ok", "stdout": "ZWJ test: 2\nZWJ-ZWJ test: 2\n", "exit_code": 0} |
| 10 | submit_patch | [] | {"status": "ok", "patch_size": 539, "files_changed": 1}full{"status": "ok", "patch_size": 539, "files_changed": 1} |
diff --git a/rich/cells.py b/rich/cells.py
index 15fe7b65..11ebb1a6 100644
--- a/rich/cells.py
+++ b/rich/cells.py
@@ -140,7 +140,7 @@ def _cell_len(text: str, unicode_version: str) -> int:
for character in iter_characters:
if character in SPECIAL:
if character == "\u200d":
- next(iter_characters)
+ pass
elif last_measured_character:
total_width += last_measured_character in cell_table.narrow_to_wide
last_measured_character = None
.............................................F
=================================== FAILURES ===================================
______ test_split_graphemes[\U0001f469\u200d\U0001f527-expected_spans5-2] ______
text = '👩\u200d🔧', expected_spans = [(0, 3, 2)], expected_cell_length = 2
@pytest.mark.parametrize(
"text,expected_spans,expected_cell_length",
[
("", [], 0),
("a", [(0, 1, 1)], 1),
("ab", [(0, 1, 1), (1, 2, 1)], 2),
("💩", [(0, 1, 2)], 2),
("わさび", [(0, 1, 2), (1, 2, 2), (2, 3, 2)], 6),
(
"👩\u200d🔧",
[(0, 3, 2)],
2,
), # 3 code points for female mechanic: female, joiner, spanner
("a👩\u200d🔧", [(0, 1, 1), (1, 4, 2)], 3),
("a👩\u200d🔧b", [(0, 1, 1), (1, 4, 2), (4, 5, 1)], 4),
("⬇", [(0, 1, 1)], 1),
("⬇️", [(0, 2, 2)], 2), # Variation selector, makes it double width
("♻", [(0, 1, 1)], 1),
("♻️", [(0, 2, 2)], 2),
("♻♻️", [(0, 1, 1), (1, 3, 2)], 3),
],
)
def test_split_graphemes(
text: str, expected_spans: list[CellSpan], expected_cell_length: int
):
spans, cell_length = split_graphemes(text)
> assert cell_len(text) == expected_cell_length
E AssertionError: assert 4 == 2
E + where 4 = cell_len('👩\u200d🔧')
tests/test_cells.py:181: AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 45 passed in 0.08s