From be8ac95c4d007ab2c7d57d167a8ff3387f70e3ad Mon Sep 17 00:00:00 2001 From: Quentin BETTOUM Date: Sun, 11 Feb 2024 18:04:03 +0100 Subject: [PATCH 1/3] Add narrow no-break space support --- book/src/configuration.md | 7 +++++-- helix-core/src/chars.rs | 2 +- helix-term/src/ui/document.rs | 9 +++++++++ helix-tui/src/widgets/reflow.rs | 17 ++++++++++++++++- helix-view/src/editor.rs | 11 +++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index a43ede76abff..5e658cc27f36 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -253,8 +253,8 @@ Options for rendering whitespace with visible characters. Use `:set whitespace.r | Key | Description | Default | |-----|-------------|---------| -| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `nbsp`, `tab`, and `newline` | `"none"` | -| `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `newline` or `tabpad` | See example below | +| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `nbsp`, `nnbsp`, `tab`, and `newline` | `"none"` | +| `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `nnbsp`, `newline` or `tabpad` | See example below | Example @@ -265,11 +265,14 @@ render = "all" [editor.whitespace.render] space = "all" tab = "all" +nbsp = "none" +nnbsp = "none" newline = "none" [editor.whitespace.characters] space = "·" nbsp = "⍽" +nnbsp = "␣" tab = "→" newline = "⏎" tabpad = "·" # Tabs will look like "→···" (depending on tab width) diff --git a/helix-core/src/chars.rs b/helix-core/src/chars.rs index 817bbb86b40c..d5cd987884f6 100644 --- a/helix-core/src/chars.rs +++ b/helix-core/src/chars.rs @@ -45,8 +45,8 @@ pub fn char_is_whitespace(ch: char) -> bool { '\u{0009}' | // Character Tabulation '\u{0020}' | // Space '\u{00A0}' | // No-break Space - '\u{180E}' | // Mongolian Vowel Separator '\u{202F}' | // Narrow No-break Space + '\u{180E}' | // Mongolian Vowel Separator '\u{205F}' | // Medium Mathematical Space '\u{3000}' | // Ideographic Space '\u{FEFF}' // Zero Width No-break Space diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index dc61ca2e3b2b..b571b83c2ddd 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -341,6 +341,7 @@ pub struct TextRenderer<'a> { pub indent_guide_style: Style, pub newline: String, pub nbsp: String, + pub nnbsp: String, pub space: String, pub tab: String, pub virtual_tab: String, @@ -395,6 +396,11 @@ impl<'a> TextRenderer<'a> { } else { " ".to_owned() }; + let nnbsp = if ws_render.nnbsp() == WhitespaceRenderValue::All { + ws_chars.nnbsp.into() + } else { + " ".to_owned() + }; let text_style = theme.get("ui.text"); @@ -405,6 +411,7 @@ impl<'a> TextRenderer<'a> { indent_guide_char: editor_config.indent_guides.character.into(), newline, nbsp, + nnbsp, space, tab, virtual_tab, @@ -448,6 +455,7 @@ impl<'a> TextRenderer<'a> { let width = grapheme.width(); let space = if is_virtual { " " } else { &self.space }; let nbsp = if is_virtual { " " } else { &self.nbsp }; + let nnbsp = if is_virtual { " " } else { &self.nnbsp }; let tab = if is_virtual { &self.virtual_tab } else { @@ -461,6 +469,7 @@ impl<'a> TextRenderer<'a> { // TODO special rendering for other whitespaces? Grapheme::Other { ref g } if g == " " => space, Grapheme::Other { ref g } if g == "\u{00A0}" => nbsp, + Grapheme::Other { ref g } if g == "\u{202F}" => nnbsp, Grapheme::Other { ref g } => g, Grapheme::Newline => &self.newline, }; diff --git a/helix-tui/src/widgets/reflow.rs b/helix-tui/src/widgets/reflow.rs index c30aa6e032a1..8ddffad53629 100644 --- a/helix-tui/src/widgets/reflow.rs +++ b/helix-tui/src/widgets/reflow.rs @@ -4,6 +4,7 @@ use helix_core::unicode::width::UnicodeWidthStr; use unicode_segmentation::UnicodeSegmentation; const NBSP: &str = "\u{00a0}"; +const NNBSP: &str = "\u{202f}"; /// A state machine to pack styled symbols into lines. /// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming @@ -58,7 +59,7 @@ impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> { let mut symbols_exhausted = true; for StyledGrapheme { symbol, style } in &mut self.symbols { symbols_exhausted = false; - let symbol_whitespace = symbol.chars().all(&char::is_whitespace) && symbol != NBSP; + let symbol_whitespace = symbol.chars().all(&char::is_whitespace) && symbol != NBSP && symbol != NNBSP; // Ignore characters wider that the total max width. if symbol.width() as u16 > self.max_line_width @@ -496,6 +497,20 @@ mod test { assert_eq!(word_wrapper_space, vec!["AAAAAAAAAAAAAAA AAAA", "AAA",]); } + #[test] + fn line_composer_word_wrapper_nnbsp() { + let width = 20; + let text = "AAAAAAAAAAAAAAA AAAA\u{202f}AAA"; + let (word_wrapper, _) = run_composer(Composer::WordWrapper { trim: true }, text, width); + assert_eq!(word_wrapper, vec!["AAAAAAAAAAAAAAA", "AAAA\u{202f}AAA",]); + + // Ensure that if the character was a regular space, it would be wrapped differently. + let text_space = text.replace('\u{202f}', " "); + let (word_wrapper_space, _) = + run_composer(Composer::WordWrapper { trim: true }, &text_space, width); + assert_eq!(word_wrapper_space, vec!["AAAAAAAAAAAAAAA AAAA", "AAA",]); + } + #[test] fn line_composer_word_wrapper_preserve_indentation() { let width = 20; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0fa6d67c9876..82ecac893856 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -676,6 +676,7 @@ pub enum WhitespaceRender { default: Option, space: Option, nbsp: Option, + nnbsp: Option, tab: Option, newline: Option, }, @@ -707,6 +708,14 @@ impl WhitespaceRender { } } } + pub fn nnbsp(&self) -> WhitespaceRenderValue { + match *self { + Self::Basic(val) => val, + Self::Specific { default, nnbsp, .. } => { + nnbsp.or(default).unwrap_or(WhitespaceRenderValue::None) + } + } + } pub fn tab(&self) -> WhitespaceRenderValue { match *self { Self::Basic(val) => val, @@ -730,6 +739,7 @@ impl WhitespaceRender { pub struct WhitespaceCharacters { pub space: char, pub nbsp: char, + pub nnbsp: char, pub tab: char, pub tabpad: char, pub newline: char, @@ -740,6 +750,7 @@ impl Default for WhitespaceCharacters { Self { space: '·', // U+00B7 nbsp: '⍽', // U+237D + nnbsp: '␣', // U+2423 tab: '→', // U+2192 newline: '⏎', // U+23CE tabpad: ' ', From 8b9f034e53327cba729da471d5ace2f071b1961d Mon Sep 17 00:00:00 2001 From: Quentin BETTOUM Date: Sun, 11 Feb 2024 21:54:34 +0100 Subject: [PATCH 2/3] Fix lint issues --- helix-tui/src/widgets/reflow.rs | 3 ++- helix-view/src/editor.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/helix-tui/src/widgets/reflow.rs b/helix-tui/src/widgets/reflow.rs index 8ddffad53629..67c4db443892 100644 --- a/helix-tui/src/widgets/reflow.rs +++ b/helix-tui/src/widgets/reflow.rs @@ -59,7 +59,8 @@ impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> { let mut symbols_exhausted = true; for StyledGrapheme { symbol, style } in &mut self.symbols { symbols_exhausted = false; - let symbol_whitespace = symbol.chars().all(&char::is_whitespace) && symbol != NBSP && symbol != NNBSP; + let symbol_whitespace = + symbol.chars().all(&char::is_whitespace) && symbol != NBSP && symbol != NNBSP; // Ignore characters wider that the total max width. if symbol.width() as u16 > self.max_line_width diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 82ecac893856..a9db2ffe5244 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -750,7 +750,7 @@ impl Default for WhitespaceCharacters { Self { space: '·', // U+00B7 nbsp: '⍽', // U+237D - nnbsp: '␣', // U+2423 + nnbsp: '␣', // U+2423 tab: '→', // U+2192 newline: '⏎', // U+23CE tabpad: ' ', From 667450faaea7889cabc5ed936ed761dc24db3d6e Mon Sep 17 00:00:00 2001 From: Quentin BETTOUM Date: Sun, 11 Feb 2024 22:57:24 +0100 Subject: [PATCH 3/3] Revert change --- helix-core/src/chars.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-core/src/chars.rs b/helix-core/src/chars.rs index d5cd987884f6..817bbb86b40c 100644 --- a/helix-core/src/chars.rs +++ b/helix-core/src/chars.rs @@ -45,8 +45,8 @@ pub fn char_is_whitespace(ch: char) -> bool { '\u{0009}' | // Character Tabulation '\u{0020}' | // Space '\u{00A0}' | // No-break Space - '\u{202F}' | // Narrow No-break Space '\u{180E}' | // Mongolian Vowel Separator + '\u{202F}' | // Narrow No-break Space '\u{205F}' | // Medium Mathematical Space '\u{3000}' | // Ideographic Space '\u{FEFF}' // Zero Width No-break Space