Skip to content

[BUG] Line breaking breaks at NBSP (U+00A0) — doesn't respect non-breaking space semantics #4196

Description

@cdxjcl

Describe the bug

Rich wraps text by tokenizing it into "words" with re_word = re.compile(r"\s*\S+\s*")
(rich/_wrap.py). In Python's default (Unicode) regex mode, \s matches U+00A0
NO-BREAK SPACE, so Rich treats NBSP as a word boundary and a line-break opportunity.

This violates the Unicode line-breaking semantics of NBSP, which is explicitly a
non-breaking space: text must not break at an NBSP. Any content that uses NBSP
to keep a run together (e.g. a kaomoji like ( ´・ω・` ), or manually joined
tokens) gets split across lines anyway.

Minimal example

from rich.console import Console

# Kaomoji whose internal spaces are NBSP (U+00A0) so it should NOT be split.
kaomoji = "( ´・ω・` )".replace(" ", "\u00a0")
# A long passage that exceeds the console width and must wrap onto several lines,
# so we can observe where the line breaks happen.
long_text = "this is a long passage that exceeds the console width and must wrap"
console = Console(width=10)
console.print(kaomoji + "  " + long_text)
  • Expected: the kaomoji stays intact on a single line as one unit; wrapping happens
    only at the regular spaces in the following long passage.
  • Actual: Rich breaks inside the kaomoji at the NBSP positions, scattering the
    kaomoji across multiple lines (the long passage after it is irrelevant to the bug —
    it only forces wrapping so the break inside the kaomoji becomes visible).

Root cause

rich/_wrap.py:

re_word = re.compile(r"\s*\S+\s*")

\s (Unicode mode) includes U+00A0; \S excludes it. So a "word" is a run of \S,
which stops at NBSP — making NBSP a break point. A NBSP should be part of the
unbreakable word, not a separator.

Expected behavior / minimal fix

Treat NBSP as a non-breaking space: include it in the word token, never a break
point.

# separator = whitespace except NBSP; word = (non-ws or NBSP)+
# the two branches \S and \xa0 are mutually exclusive -> linear time, no backtracking
re_word = re.compile(r"[^\S\xa0]*(?:\S|\xa0)+[^\S\xa0]*")

Platform

  • Rich: 15.0.0
  • Python: 3.14.6 (CPython, MSC v.1944 64 bit AMD64)
  • OS / terminal: Windows 11 (10.0.26200) / Windows Terminal

python -m rich.diagnose

╭───────────────────────── <class 'rich.console.Console'> ─────────────────────────╮
│ A high level console interface.                                                  │
│                                                                                  │
│ ╭──────────────────────────────────────────────────────────────────────────────╮ │
│ │ <console width=119 ColorSystem.TRUECOLOR>                                    │ │
│ ╰──────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                  │
│     color_system = 'truecolor'                                                   │
│         encoding = 'utf-8'                                                       │
│             file = <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'> │
│           height = 40                                                            │
│    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=119, height=40),             │
│                        legacy_windows=False,                                     │
│                        min_width=1,                                              │
│                        max_width=119,                                            │
│                        is_terminal=True,                                         │
│                        encoding='utf-8',                                         │
│                        max_height=40,                                            │
│                        justify=None,                                             │
│                        overflow=None,                                            │
│                        no_wrap=False,                                            │
│                        highlight=None,                                           │
│                        markup=None,                                              │
│                        height=None                                               │
│                    )                                                             │
│            quiet = False                                                         │
│           record = False                                                         │
│         safe_box = True                                                          │
│             size = ConsoleDimensions(width=119, height=40)                       │
│        soft_wrap = False                                                         │
│           stderr = False                                                         │
│            style = None                                                          │
│         tab_size = 8                                                             │
│            width = 119                                                           │
╰──────────────────────────────────────────────────────────────────────────────────╯
╭── <class 'rich._windows.WindowsConsoleFeatures'> ───╮
│ Windows features available.                         │
│                                                     │
│ ╭─────────────────────────────────────────────────╮ │
│ │ WindowsConsoleFeatures(vt=True, truecolor=True) │ │
│ ╰─────────────────────────────────────────────────╯ │
│                                                     │
│ truecolor = True                                    │
│        vt = True                                    │
╰─────────────────────────────────────────────────────╯
╭────── 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': None,                  │
│     'TTY_COMPATIBLE': None,        │
│     'TTY_INTERACTIVE': None,       │
│     'VSCODE_VERBOSE_LOGGING': None │
│ }                                  │
╰────────────────────────────────────╯
platform="Windows"

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