editor: Preserve non-breaking whitespace during rewrap (#59893)

## Objective

Prevent rewrap from breaking at whitespace that Unicode classifies as
non-breaking.

The original approach, prompted by #59664, treated every non-ASCII
whitespace character as unbreakable. Unicode classifies `U+2009 THIN
SPACE` as breakable and `U+202F NARROW NO-BREAK SPACE` as its
non-breaking counterpart.

## Solution

- Treat `U+00A0 NO-BREAK SPACE`, `U+2007 FIGURE SPACE`, and `U+202F
NARROW NO-BREAK SPACE` as part of the surrounding word
- Keep thin spaces, em spaces, and other Unicode breaking spaces as
line-break opportunities
- Cover all three non-breaking whitespace variants in tokenizer and
wrapping tests

## Testing

- `cargo test -p editor rewrap::tests`

Release Notes:

- Fixed rewrap breaking lines at non-breaking spaces.

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Aliaksandr ZhyburtovichandNathan Sobo authored and GitHub committed 2026-07-15 22:17:51 +00:00
1 parent af01c321fa
commit 9dfdaa62b7
1 file changed
+37 -2
+37 -2
View File
@@ -430,8 +430,13 @@ fn is_grapheme_ideographic(text: &str) -> bool {
text.chars().any(is_char_ideographic)
}
fn is_non_breaking_whitespace(character: char) -> bool {
matches!(character, '\u{00A0}' | '\u{2007}' | '\u{202F}')
}
fn is_grapheme_whitespace(text: &str) -> bool {
text.chars().any(|x| x.is_whitespace())
text.chars()
.any(|character| character.is_whitespace() && !is_non_breaking_whitespace(character))
}
fn should_stay_with_preceding_ideograph(text: &str) -> bool {
@@ -690,7 +695,23 @@ mod tests {
word("笔", 1),
],
),
(" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
(
"\u{2003}mutton",
&[whitespace("\u{2003}", 1), word("mutton", 6)],
),
(
"a\u{2009}b\u{2009}c",
&[
word("a", 1),
whitespace("\u{2009}", 1),
word("b", 1),
whitespace("\u{2009}", 1),
word("c", 1),
],
),
("a\u{a0}b\u{a0}c", &[word("a\u{a0}b\u{a0}c", 5)]),
("a\u{2007}b\u{2007}c", &[word("a\u{2007}b\u{2007}c", 5)]),
("a\u{202f}b\u{202f}c", &[word("a\u{202f}b\u{202f}c", 5)]),
];
fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
@@ -778,5 +799,19 @@ mod tests {
),
format!("foo{}bar", '\u{2009}')
);
for non_breaking_space in ['\u{a0}', '\u{2007}', '\u{202f}'] {
let input = format!("a{0}a{0}a{0}a{0}a", non_breaking_space);
assert_eq!(
wrap_with_prefix(
String::new(),
String::new(),
input.clone(),
4,
NonZeroU32::new(4).unwrap(),
false,
),
input
);
}
}
}