← eval_submission_analyzer

rich_3953

failed WRONG_FIX wrong_fix · 9 tool calls · 46 s · Textualize/rich

Task input

Fix ZWJ and edge cases

Fixes https://github.com/Textualize/rich/issues/3947
Fixes https://github.com/Textualize/rich/issues/3950

Tool calls (10)

#ToolArgumentsResult
1run_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.p
full
{"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_
2run_command
{
 "command": "grep -rn \"200d\" ."
}
{"status": "ok", "stdout": "./benchmarks/results/darrenburns-2022-mbp/666d0cf2-virtualenv-py3.10.json:1:{\"commit_hash\": \"666d0cf2b2eca98ff75bd260f91b200d0bb8
full
{"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
3run_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/test
full
{"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}
4read_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 itemgetter
full
{"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  
5read_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 | No
full
{"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}
6read_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}
7edit_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 fo
full
{"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}
8run_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 recen
full
{"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}}
9run_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}
10submit_patch
[]
{"status": "ok", "patch_size": 539, "files_changed": 1}
full
{"status": "ok", "patch_size": 539, "files_changed": 1}

Patch

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

Test output

show
.............................................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