Skip to content

[BUG] If the text contains '\u200d', console.print may throw a RuntimeError #3947

@xiangxiangxiong9

Description

@xiangxiangxiong9

Describe the bug

As the title says

from rich import get_console
console = get_console()
console.print('1\u200d2') 

# --- Or a simpler one ---
from rich import print as rich_print
rich_print('\u200d') 

# --- This can output normally, but b has been stretched---
from rich import print as rich_print
rich_print('a\u200db') 

# --- But this still throws a RuntimeError --
from rich import print as rich_print
rich_print('a\u200d\nb') 

# --- Including this...---
from rich import print as rich_print
rich_print('a\u200db(') 

Perhaps it's missing a check for whether \u200d is the last character here?

rich/rich/cells.py

Lines 138 to 150 in 1d402e0

iter_characters = iter(text)
for character in iter_characters:
if character in SPECIAL:
if character == "\u200d":
next(iter_characters)
elif last_measured_character:
total_width += last_measured_character in cell_table.narrow_to_wide
last_measured_character = None
else:
if character_width := get_character_cell_size(character, unicode_version):
last_measured_character = character
total_width += character_width

Traceback (show_locals=True)

Click to expand
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/segment.py:375 in <genexpr>              │
│                                                                                                  │
│   372 │   │   Returns:                                                                           │
│   373 │   │   │   List[Segment]: A line of segments with the desired length.                     │
│   374 │   │   """                                                                                │
│ ❱ 375 │   │   line_length = sum(segment.cell_length for segment in line)                         │
│   376 │   │   new_line: List[Segment]                                                            │
│   377 │   │                                                                                      │
│   378 │   │   if line_length < length:                                                           │
│                                                                                                  │
│ ╭───────────────────── locals ─────────────────────╮                                             │
│ │      .0 = <list_iterator object at 0x74b7f85870> │                                             │
│ │ segment = Segment('\u200d', Style())             │                                             │
│ ╰──────────────────────────────────────────────────╯                                             │
│                                                                                                  │
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/segment.py:89 in cell_length             │
│                                                                                                  │
│    86 │   │   │   int: A number of cells.                                                        │
│    87 │   │   """                                                                                │
│    88 │   │   text, _style, control = self                                                       │
│ ❱  89 │   │   return 0 if control else cell_len(text)                                            │
│    90 │                                                                                          │
│    91 │   def __rich_repr__(self) -> Result:                                                     │
│    92 │   │   yield self.text                                                                    │
│                                                                                                  │
│ ╭─────────────── locals ───────────────╮                                                         │
│ │  _style = Style()                    │                                                         │
│ │ control = None                       │                                                         │
│ │    self = Segment('\u200d', Style()) │                                                         │
│ │    text = '\u200d'                   │                                                         │
│ ╰──────────────────────────────────────╯                                                         │
│                                                                                                  │
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/cells.py:106 in cell_len                 │
│                                                                                                  │
│   103 │   │   Length of string in terminal cells.                                                │
│   104 │   """                                                                                    │
│   105 │   if len(text) < 512:                                                                    │
│ ❱ 106 │   │   return cached_cell_len(text, unicode_version)                                      │
│   107 │   return _cell_len(text, unicode_version)                                                │
│   108                                                                                            │
│   109                                                                                            │
│                                                                                                  │
│ ╭────────── locals ──────────╮                                                                   │
│ │            text = '\u200d' │                                                                   │
│ │ unicode_version = 'auto'   │                                                                   │
│ ╰────────────────────────────╯                                                                   │
│                                                                                                  │
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/cells.py:92 in cached_cell_len           │
│                                                                                                  │
│    89 │   Returns:                                                                               │
│    90 │   │   int: Get the number of cells required to display text.                             │
│    91 │   """                                                                                    │
│ ❱  92 │   return _cell_len(text, unicode_version)                                                │
│    93                                                                                            │
│    94                                                                                            │
│    95 def cell_len(text: str, unicode_version: str = "auto") -> int:                             │
│                                                                                                  │
│ ╭────────── locals ──────────╮                                                                   │
│ │            text = '\u200d' │                                                                   │
│ │ unicode_version = 'auto'   │                                                                   │
│ ╰────────────────────────────╯                                                                   │
│                                                                                                  │
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/cells.py:143 in _cell_len                │
│                                                                                                  │
│   140 │   for character in iter_characters:                                                      │
│   141 │   │   if character in SPECIAL:                                                           │
│   142 │   │   │   if character == "\u200d":                                                      │
│ ❱ 143 │   │   │   │   next(iter_characters)                                                      │
│   144 │   │   │   elif last_measured_character:                                                  │
│   145 │   │   │   │   total_width += last_measured_character in cell_table.narrow_to_wide        │
│   146 │   │   │   │   last_measured_character = None                                             │
│                                                                                                  │
│ ╭──────────────────────────── locals ─────────────────────────────╮                              │
│ │              cell_table = CellTable(                            │                              │
│ │                           │   unicode_version='17.0.0',         │                              │
│ │                           │   widths=[                          │                              │
│ │                           │   │   (0, 0, 0),                    │                              │
│ │                           │   │   (768, 879, 0),                │                              │
│ │                           │   │   (1155, 1161, 0),              │                              │
│ │                           │   │   (1425, 1469, 0),              │                              │
│ │                           │   │   (1471, 1471, 0),              │                              │
│ │                           │   │   (1473, 1474, 0),              │                              │
│ │                           │   │   (1476, 1477, 0),              │                              │
│ │                           │   │   (1479, 1479, 0),              │                              │
│ │                           │   │   (1552, 1562, 0),              │                              │
│ │                           │   │   (1564, 1564, 0),              │                              │
│ │                           │   │   ... +454                      │                              │
│ │                           │   ],                                │                              │
│ │                           │   narrow_to_wide=frozenset({        │                              │
│ │                           │   │   '↙',                          │                              │
│ │                           │   │   '⚜',                          │                              │
│ │                           │   │   '🛳',                          │                              │
│ │                           │   │   '🏝',                          │                              │
│ │                           │   │   '☝',                          │                              │
│ │                           │   │   '🖊',                          │                              │
│ │                           │   │   '🖌',                          │                              │
│ │                           │   │   '🌨',                          │                              │
│ │                           │   │   '🖨',                          │                              │
│ │                           │   │   '🗒',                          │                              │
│ │                           │   │   ... +203                      │                              │
│ │                           │   })                                │                              │
│ │                           )                                     │                              │
│ │               character = '\u200d'                              │                              │
│ │         iter_characters = <str_iterator object at 0x74b6265b40> │                              │
│ │ last_measured_character = None                                  │                              │
│ │                 SPECIAL = {'️', '\u200d'}                        │                              │
│ │                    text = '\u200d'                              │                              │
│ │             total_width = 0                                     │                              │
│ │         unicode_version = 'auto'                                │                              │
│ ╰─────────────────────────────────────────────────────────────────╯                              │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
StopIteration

The above exception was the direct cause of the following exception:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ in <module>:1                                                                                    │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │    asyncio = <module 'asyncio' from                                                          │ │
│ │              '/usr/local/python3.14/lib/python3.14/asyncio/__init__.py'>                     │ │
│ │    console = <console width=201 ColorSystem.EIGHT_BIT>                                       │ │
│ │       rich = <module 'rich' from                                                             │ │
│ │              '/usr/local/python3.14/lib/python3.14/site-packages/rich/__init__.py'>          │ │
│ │ rich_print = <bound method Console.print of <console width=201 ColorSystem.EIGHT_BIT>>       │ │
│ │  threading = <module 'threading' from '/usr/local/python3.14/lib/python3.14/threading.py'>   │ │
│ │     typing = <module 'typing' from '/usr/local/python3.14/lib/python3.14/typing.py'>         │ │
│ │       uuid = <module 'uuid' from '/usr/local/python3.14/lib/python3.14/uuid.py'>             │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/__init__.py:74 in print                  │
│                                                                                                  │
│    71 │   from .console import Console                                                           │
│    72 │                                                                                          │
│    73 │   write_console = get_console() if file is None else Console(file=file)                  │
│ ❱  74 │   return write_console.print(*objects, sep=sep, end=end)                                 │
│    75                                                                                            │
│    76                                                                                            │
│    77 def print_json(                                                                            │
│                                                                                                  │
│ ╭───────────────────────── locals ──────────────────────────╮                                    │
│ │           end = '\n'                                      │                                    │
│ │          file = None                                      │                                    │
│ │         flush = False                                     │                                    │
│ │       objects = ('1\u200d2',)                             │                                    │
│ │           sep = ' '                                       │                                    │
│ │ write_console = <console width=201 ColorSystem.EIGHT_BIT> │                                    │
│ ╰───────────────────────────────────────────────────────────╯                                    │
│                                                                                                  │
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/console.py:1744 in print                 │
│                                                                                                  │
│   1741 │   │   │   │   │   new_segments.insert(0, Segment.line())                                │
│   1742 │   │   │   if crop:                                                                      │
│   1743 │   │   │   │   buffer_extend = self._buffer.extend                                       │
│ ❱ 1744 │   │   │   │   for line in Segment.split_and_crop_lines(                                 │
│   1745 │   │   │   │   │   new_segments, self.width, pad=False                                   │
│   1746 │   │   │   │   ):                                                                        │
│   1747 │   │   │   │   │   buffer_extend(line)                                                   │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │  buffer_extend = <built-in method extend of list object at 0x74b62903c0>                     │ │
│ │           crop = True                                                                        │ │
│ │          emoji = None                                                                        │ │
│ │            end = '\n'                                                                        │ │
│ │         extend = <built-in method extend of list object at 0x74b60bd0c0>                     │ │
│ │         height = None                                                                        │ │
│ │      highlight = None                                                                        │ │
│ │        justify = None                                                                        │ │
│ │         markup = None                                                                        │ │
│ │ new_line_start = False                                                                       │ │
│ │   new_segments = [                                                                           │ │
│ │                  │   Segment(                                                                │ │
│ │                  │   │   '1',                                                                │ │
│ │                  │   │   Style(                                                              │ │
│ │                  │   │   │   color=Color('cyan', ColorType.STANDARD, number=6),              │ │
│ │                  │   │   │   bold=True,                                                      │ │
│ │                  │   │   │   italic=False                                                    │ │
│ │                  │   │   )                                                                   │ │
│ │                  │   ),                                                                      │ │
│ │                  │   Segment('\u200d', Style()),                                             │ │
│ │                  │   Segment(                                                                │ │
│ │                  │   │   '2',                                                                │ │
│ │                  │   │   Style(                                                              │ │
│ │                  │   │   │   color=Color('cyan', ColorType.STANDARD, number=6),              │ │
│ │                  │   │   │   bold=True,                                                      │ │
│ │                  │   │   │   italic=False                                                    │ │
│ │                  │   │   )                                                                   │ │
│ │                  │   ),                                                                      │ │
│ │                  │   Segment('\n')                                                           │ │
│ │                  ]                                                                           │ │
│ │        no_wrap = None                                                                        │ │
│ │        objects = ('1\u200d2',)                                                               │ │
│ │       overflow = None                                                                        │ │
│ │         render = <bound method Console.render of <console width=201 ColorSystem.EIGHT_BIT>>  │ │
│ │   render_hooks = []                                                                          │ │
│ │ render_options = ConsoleOptions(                                                             │ │
│ │                  │   size=ConsoleDimensions(width=201, height=42),                           │ │
│ │                  │   legacy_windows=False,                                                   │ │
│ │                  │   min_width=1,                                                            │ │
│ │                  │   max_width=201,                                                          │ │
│ │                  │   is_terminal=True,                                                       │ │
│ │                  │   encoding='utf-8',                                                       │ │
│ │                  │   max_height=42,                                                          │ │
│ │                  │   justify=None,                                                           │ │
│ │                  │   overflow=None,                                                          │ │
│ │                  │   no_wrap=None,                                                           │ │
│ │                  │   highlight=None,                                                         │ │
│ │                  │   markup=None,                                                            │ │
│ │                  │   height=None                                                             │ │
│ │                  )                                                                           │ │
│ │     renderable = <text '1\u200d2' [Span(0, 1, 'repr.number'), Span(2, 3, 'repr.number')] ''> │ │
│ │    renderables = [                                                                           │ │
│ │                  │   <text '1\u200d2' [Span(0, 1, 'repr.number'), Span(2, 3, 'repr.number')] │ │
│ │                  ''>                                                                         │ │
│ │                  ]                                                                           │ │
│ │           self = <console width=201 ColorSystem.EIGHT_BIT>                                   │ │
│ │            sep = ' '                                                                         │ │
│ │      soft_wrap = False                                                                       │ │
│ │          style = None                                                                        │ │
│ │          width = None                                                                        │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/segment.py:344 in split_and_crop_lines   │
│                                                                                                  │
│   341 │   │   │   │   │   if _text:                                                              │
│   342 │   │   │   │   │   │   append(cls(_text, segment_style))                                  │
│   343 │   │   │   │   │   if new_line:                                                           │
│ ❱ 344 │   │   │   │   │   │   cropped_line = adjust_line_length(                                 │
│   345 │   │   │   │   │   │   │   line, length, style=style, pad=pad                             │
│   346 │   │   │   │   │   │   )                                                                  │
│   347 │   │   │   │   │   │   if include_new_lines:                                              │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │                  _ = None                                                                    │ │
│ │              _text = ''                                                                      │ │
│ │ adjust_line_length = <bound method Segment.adjust_line_length of <class                      │ │
│ │                      'rich.segment.Segment'>>                                                │ │
│ │             append = <built-in method append of list object at 0x74b60ae640>                 │ │
│ │  include_new_lines = True                                                                    │ │
│ │             length = 201                                                                     │ │
│ │               line = [                                                                       │ │
│ │                      │   Segment(                                                            │ │
│ │                      │   │   '1',                                                            │ │
│ │                      │   │   Style(                                                          │ │
│ │                      │   │   │   color=Color('cyan', ColorType.STANDARD, number=6),          │ │
│ │                      │   │   │   bold=True,                                                  │ │
│ │                      │   │   │   italic=False                                                │ │
│ │                      │   │   )                                                               │ │
│ │                      │   ),                                                                  │ │
│ │                      │   Segment('\u200d', Style()),                                         │ │
│ │                      │   Segment(                                                            │ │
│ │                      │   │   '2',                                                            │ │
│ │                      │   │   Style(                                                          │ │
│ │                      │   │   │   color=Color('cyan', ColorType.STANDARD, number=6),          │ │
│ │                      │   │   │   bold=True,                                                  │ │
│ │                      │   │   │   italic=False                                                │ │
│ │                      │   │   )                                                               │ │
│ │                      │   )                                                                   │ │
│ │                      ]                                                                       │ │
│ │           new_line = '\n'                                                                    │ │
│ │   new_line_segment = Segment('\n')                                                           │ │
│ │                pad = False                                                                   │ │
│ │            segment = Segment('\n')                                                           │ │
│ │      segment_style = None                                                                    │ │
│ │           segments = [                                                                       │ │
│ │                      │   Segment(                                                            │ │
│ │                      │   │   '1',                                                            │ │
│ │                      │   │   Style(                                                          │ │
│ │                      │   │   │   color=Color('cyan', ColorType.STANDARD, number=6),          │ │
│ │                      │   │   │   bold=True,                                                  │ │
│ │                      │   │   │   italic=False                                                │ │
│ │                      │   │   )                                                               │ │
│ │                      │   ),                                                                  │ │
│ │                      │   Segment('\u200d', Style()),                                         │ │
│ │                      │   Segment(                                                            │ │
│ │                      │   │   '2',                                                            │ │
│ │                      │   │   Style(                                                          │ │
│ │                      │   │   │   color=Color('cyan', ColorType.STANDARD, number=6),          │ │
│ │                      │   │   │   bold=True,                                                  │ │
│ │                      │   │   │   italic=False                                                │ │
│ │                      │   │   )                                                               │ │
│ │                      │   ),                                                                  │ │
│ │                      │   Segment('\n')                                                       │ │
│ │                      ]                                                                       │ │
│ │              style = None                                                                    │ │
│ │               text = ''                                                                      │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ /usr/local/python3.14/lib/python3.14/site-packages/rich/segment.py:375 in adjust_line_length     │
│                                                                                                  │
│   372 │   │   Returns:                                                                           │
│   373 │   │   │   List[Segment]: A line of segments with the desired length.                     │
│   374 │   │   """                                                                                │
│ ❱ 375 │   │   line_length = sum(segment.cell_length for segment in line)                         │
│   376 │   │   new_line: List[Segment]                                                            │
│   377 │   │                                                                                      │
│   378 │   │   if line_length < length:                                                           │
│                                                                                                  │
│ ╭──────────────────────────────── locals ─────────────────────────────────╮                      │
│ │ length = 201                                                            │                      │
│ │   line = [                                                              │                      │
│ │          │   Segment(                                                   │                      │
│ │          │   │   '1',                                                   │                      │
│ │          │   │   Style(                                                 │                      │
│ │          │   │   │   color=Color(                                       │                      │
│ │          │   │   │   │   'cyan',                                        │                      │
│ │          │   │   │   │   ColorType.STANDARD,                            │                      │
│ │          │   │   │   │   number=6                                       │                      │
│ │          │   │   │   ),                                                 │                      │
│ │          │   │   │   bold=True,                                         │                      │
│ │          │   │   │   italic=False                                       │                      │
│ │          │   │   )                                                      │                      │
│ │          │   ),                                                         │                      │
│ │          │   Segment('\u200d', Style()),                                │                      │
│ │          │   Segment(                                                   │                      │
│ │          │   │   '2',                                                   │                      │
│ │          │   │   Style(                                                 │                      │
│ │          │   │   │   color=Color(                                       │                      │
│ │          │   │   │   │   'cyan',                                        │                      │
│ │          │   │   │   │   ColorType.STANDARD,                            │                      │
│ │          │   │   │   │   number=6                                       │                      │
│ │          │   │   │   ),                                                 │                      │
│ │          │   │   │   bold=True,                                         │                      │
│ │          │   │   │   italic=False                                       │                      │
│ │          │   │   )                                                      │                      │
│ │          │   )                                                          │                      │
│ │          ]                                                              │                      │
│ │    pad = False                                                          │                      │
│ │  style = None                                                           │                      │
│ ╰─────────────────────────────────────────────────────────────────────────╯                      │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
RuntimeError: generator raised StopIteration

Platform

Click to expand
╭───────────────────────── <class 'rich.console.Console'> ─────────────────────────╮
│ A high level console interface.                                                  │
│                                                                                  │
│ ╭──────────────────────────────────────────────────────────────────────────────╮ │
│ │ <console width=201 ColorSystem.EIGHT_BIT>                                    │ │
│ ╰──────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                  │
│     color_system = '256'                                                         │
│         encoding = 'utf-8'                                                       │
│             file = <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'> │
│           height = 42                                                            │
│    is_alt_screen = False                                                         │
│ is_dumb_terminal = False                                                         │
│   is_interactive = True                                                          │
│       is_jupyter = False                                                         │
│      is_terminal = True                                                          │
│   legacy_windows = False                                                         │
│         no_color = False                                                         │
│          options = ConsoleOptions(                                               │
│                        size=ConsoleDimensions(width=201, height=42),             │
│                        legacy_windows=False,                                     │
│                        min_width=1,                                              │
│                        max_width=201,                                            │
│                        is_terminal=True,                                         │
│                        encoding='utf-8',                                         │
│                        max_height=42,                                            │
│                        justify=None,                                             │
│                        overflow=None,                                            │
│                        no_wrap=False,                                            │
│                        highlight=None,                                           │
│                        markup=None,                                              │
│                        height=None                                               │
│                    )                                                             │
│            quiet = False                                                         │
│           record = False                                                         │
│         safe_box = True                                                          │
│             size = ConsoleDimensions(width=201, height=42)                       │
│        soft_wrap = False                                                         │
│           stderr = False                                                         │
│            style = None                                                          │
│         tab_size = 8                                                             │
│            width = 201                                                           │
╰──────────────────────────────────────────────────────────────────────────────────╯
╭─── <class 'rich._windows.WindowsConsoleFeatures'> ────╮
│ Windows features available.                           │
│                                                       │
│ ╭───────────────────────────────────────────────────╮ │
│ │ WindowsConsoleFeatures(vt=False, truecolor=False) │ │
│ ╰───────────────────────────────────────────────────╯ │
│                                                       │
│ truecolor = False                                     │
│        vt = False                                     │
╰───────────────────────────────────────────────────────╯
╭────── Environment Variables ───────╮
│ {                                  │
│     'CLICOLOR': None,              │
│     'COLORTERM': None,             │
│     'COLUMNS': None,               │
│     'JPY_PARENT_PID': None,        │
│     'JUPYTER_COLUMNS': None,       │
│     'JUPYTER_LINES': None,         │
│     'LINES': None,                 │
│     'NO_COLOR': None,              │
│     'TERM_PROGRAM': None,          │
│     'TERM': 'xterm-256color',      │
│     'TTY_COMPATIBLE': None,        │
│     'TTY_INTERACTIVE': None,       │
│     'VSCODE_VERBOSE_LOGGING': None │
│ }                                  │
╰────────────────────────────────────╯
platform="Linux"
rich==14.3.1

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions