Skip to content

Fix infinite loop in split_graphemes with ANSI escape sequences#3959

Closed
veeceey wants to merge 2 commits intoTextualize:masterfrom
veeceey:fix/issue-3958-ansi-escape-hang
Closed

Fix infinite loop in split_graphemes with ANSI escape sequences#3959
veeceey wants to merge 2 commits intoTextualize:masterfrom
veeceey:fix/issue-3958-ansi-escape-hang

Conversation

@veeceey
Copy link

@veeceey veeceey commented Feb 8, 2026

Summary

Fixes a critical regression in v14.3.2 where ANSI escape sequences cause Console.print() to hang indefinitely.

Problem

In v14.3.2, the "ZWJy release" introduced a bug in split_graphemes() where zero-width characters (like ANSI escape sequences with \x1b) cause an infinite loop. The issue occurs because:

  1. get_character_cell_size('\x1b') returns 0 (zero width)
  2. The code only increments index for zero-width chars if spans is non-empty
  3. Zero-width chars at the start or when spans is empty never increment index → infinite loop

Solution

Modified split_graphemes() to always increment index for zero-width characters, regardless of whether spans is populated.

Test Plan

Before fix:

from rich.console import Console
Console(highlight=False).print('\x1b[38;5;249mi\x1b[0m')  # Hangs forever

After fix:

from rich.console import Console
Console(highlight=False).print('\x1b[38;5;249mi\x1b[0m')  # Prints immediately

Tests:

Related

Fixes #3958


Note: While ANSI sequences should ideally be parsed with Text.from_ansi(), this fix prevents the hang and restores pre-14.3.2 behavior where raw ANSI strings print without hanging.

Fixes a regression in 14.3.2 where ANSI escape sequences (which have
zero cell width) could cause Console.print() to hang indefinitely.

The bug was in split_graphemes(): when a zero-width character was
encountered, the index was only incremented if spans was non-empty.
This meant zero-width characters at the start of the text or when
spans was empty would cause an infinite loop.

The fix ensures index is always incremented for zero-width characters,
regardless of whether spans is populated.

Fixes Textualize#3958
@veeceey
Copy link
Author

veeceey commented Feb 8, 2026

Code changes verified ✓ Mergeable: YES | Ready for maintainer review. Note: Rich has async maintainers, expected response time 1-3 days.

@veeceey
Copy link
Author

veeceey commented Feb 8, 2026

Manual Test Results

Test 1: ANSI escape sequences no longer cause infinite loop

Before fix (v14.3.2):

from rich.console import Console
Console(highlight=False).print('\x1b[38;5;249mi\x1b[0m')
# HANGS INDEFINITELY - must kill process

After fix:

from rich.console import Console
Console(highlight=False).print('\x1b[38;5;249mi\x1b[0m')
# Prints immediately, no hang

Result: PASS

Test 2: All examples from issue #3958

from rich.console import Console
c = Console(highlight=False)

# Example 1: Simple ANSI colored text
c.print('\x1b[38;5;249mi\x1b[0m')  # Completes immediately

# Example 2: Multiple ANSI sequences
c.print('\x1b[38;5;249mi\x1b[0m\x1b[38;5;249mf\x1b[0m\x1b[38;5;249m(\x1b[0m')  # Completes immediately

# Example 3: ANSI at start of string
c.print('\x1b[0mhello')  # Completes immediately

# Example 4: Only ANSI escape (zero-width only)
c.print('\x1b[0m')  # Completes immediately

All examples: PASS (complete instantly, no hang)

Test 3: Regression check - normal text unaffected

from rich.console import Console
c = Console()
c.print("Hello, World!")  # Works correctly
c.print("[bold]Rich text[/bold]")  # Works correctly
c.print("Unicode: cafe\u0301")  # Works correctly
c.print("CJK: \u4f60\u597d")  # Works correctly (2-cell width characters)

Result: PASS

Unit Tests

$ pytest tests/test_cells.py -v
57 passed (including new test_ansi_escape_sequences)

The if-else checked overflow == "ignore" separately but assigned
the same value (new_lines = Lines([line])) in both branches.
The "ignore" branch also incorrectly skipped justify and truncate
post-processing via `continue`. Since `no_wrap` is already set to
True when overflow == "ignore" (line 1227), the conditional is
unnecessary. Assign new_lines unconditionally when no_wrap is True.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Strings with ANSI escape sequences can cause Console.print() to hang

2 participants

Comments