From 9dfdaa62b72a1afb45d33df0204db3945e686c8e Mon Sep 17 00:00:00 2001 From: Aliaksandr Zhyburtovich Date: Thu, 16 Jul 2026 00:17:51 +0200 Subject: [PATCH] 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 --- crates/editor/src/rewrap.rs | 39 +++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/rewrap.rs b/crates/editor/src/rewrap.rs index 50647729d3..9f39af1fcc 100644 --- a/crates/editor/src/rewrap.rs +++ b/crates/editor/src/rewrap.rs @@ -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 + ); + } } }