Skip to content

Do not duplicate a non-ASCII space when wrapping - #275

Open
dylanpulver wants to merge 1 commit into
matthewwithanm:developfrom
dylanpulver:fix-nbsp-duplicated-when-wrapping
Open

Do not duplicate a non-ASCII space when wrapping#275
dylanpulver wants to merge 1 commit into
matthewwithanm:developfrom
dylanpulver:fix-nbsp-duplicated-when-wrapping

Conversation

@dylanpulver

@dylanpulver dylanpulver commented Sep 2, 2026

Copy link
Copy Markdown

convert_p's wrap branch captures trailing whitespace with a bare rstrip() and re-appends it after fill():

line_no_trailing = line.rstrip()
trailing = line[len(line_no_trailing):]
line = fill(line, ...)
new_lines.append(line + trailing)

Bare rstrip() removes everything str.isspace() accepts, but fill() only strips the ASCII set — so a trailing U+00A0 is left in place by fill() and appended again. One   in, two out.

<p>a&nbsp;<br>c</p>   wrap=False -> 'a\xa0  \nc'
                      wrap=True  -> 'a\xa0\xa0  \nc'

What I ran. With a wrap_width too wide to wrap anything, wrap=True should equal wrap=False. 96 documents — 8 trailing characters × 3 bodies × 2 newline_style × with/without <br> — compared between the two settings, same command on develop and on the patch:

mismatches
develop (bc98c9e) 18/96
patched 0/96

All 18 are duplications, in six characters: U+00A0, U+202F, U+2007, U+2003, U+2009, U+3000. ASCII space and the no-trailing-character case are the controls and match in both trees. pytest 83 passed before and after; the added assertion fails on unpatched source. flake8 --ignore=E501,W503 clean.

The alternative I rejected. Wrapping line_no_trailing instead of line also gives 0/96, and it's arguably the more obvious reading. It differs once the line actually wraps: at wrap_width=8, <p>aaa bbbb&nbsp;<br>c</p> gives

  • this patch: aaa\nbbbb\xa0 — longest content line 5
  • that one: aaa bbbb\xa0 — longest content line 9, over the requested width

because it hands fill() a string with the U+00A0 already removed, so the width calculation doesn't see it. A no-break space occupies a column like any other, so I kept it inside the fill() input. Happy to switch if you'd rather.

Deliberately not touched. The rstrip() in process_text is the other half of the pair #188 converted, and #188 said explicitly that the trailing side is an open question — "or, conversely, where trailing such spaces could safely be stripped even if they no longer are after this change" — so that one is yours to decide, and this change doesn't depend on it. This also does not fix #175: that report goes through chomp(), which is untouched here (<div>this is a <i>test&nbsp;</i>with whitespaces</div> is unchanged by this patch).

AI assistance: found and patched with Claude Code (Claude Opus 5, claude-opus-5); I reviewed and ran everything above myself.

In convert_p's wrap branch, `trailing` is captured with a bare rstrip()
and then appended after fill(). Bare rstrip() removes every character
str.isspace() calls whitespace, but fill() only strips the ASCII ones,
so a trailing U+00A0 (or U+202F, U+2007, U+2003, U+2009, U+3000) is left
in place by fill() and then appended a second time.

`<p>a&nbsp;<br>c</p>` with wrap=True gives 'a\xa0\xa0  \nc' where
wrap=False gives 'a\xa0  \nc'. Restricting the rstrip to the same
' \t\r\n' set fill() uses makes the two agree.
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.

Inconsistent handling of No-Break Space and Space

1 participant