Add narrow no-break space support (#9604)

This commit is contained in:
Quentin 2024-03-25 02:29:36 +01:00 committed by GitHub
parent 2d9e336f64
commit 614a744d24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 42 additions and 3 deletions

View file

@ -255,8 +255,8 @@ Options for rendering whitespace with visible characters. Use `:set whitespace.r
| Key | Description | Default | | 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"` | | `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`, `newline` or `tabpad` | See example below | | `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 Example
@ -267,11 +267,14 @@ render = "all"
[editor.whitespace.render] [editor.whitespace.render]
space = "all" space = "all"
tab = "all" tab = "all"
nbsp = "none"
nnbsp = "none"
newline = "none" newline = "none"
[editor.whitespace.characters] [editor.whitespace.characters]
space = "·" space = "·"
nbsp = "⍽" nbsp = "⍽"
nnbsp = "␣"
tab = "→" tab = "→"
newline = "⏎" newline = "⏎"
tabpad = "·" # Tabs will look like "→···" (depending on tab width) tabpad = "·" # Tabs will look like "→···" (depending on tab width)

View file

@ -341,6 +341,7 @@ pub struct TextRenderer<'a> {
pub indent_guide_style: Style, pub indent_guide_style: Style,
pub newline: String, pub newline: String,
pub nbsp: String, pub nbsp: String,
pub nnbsp: String,
pub space: String, pub space: String,
pub tab: String, pub tab: String,
pub virtual_tab: String, pub virtual_tab: String,
@ -395,6 +396,11 @@ impl<'a> TextRenderer<'a> {
} else { } else {
" ".to_owned() " ".to_owned()
}; };
let nnbsp = if ws_render.nnbsp() == WhitespaceRenderValue::All {
ws_chars.nnbsp.into()
} else {
" ".to_owned()
};
let text_style = theme.get("ui.text"); let text_style = theme.get("ui.text");
@ -405,6 +411,7 @@ impl<'a> TextRenderer<'a> {
indent_guide_char: editor_config.indent_guides.character.into(), indent_guide_char: editor_config.indent_guides.character.into(),
newline, newline,
nbsp, nbsp,
nnbsp,
space, space,
tab, tab,
virtual_tab, virtual_tab,
@ -448,6 +455,7 @@ impl<'a> TextRenderer<'a> {
let width = grapheme.width(); let width = grapheme.width();
let space = if is_virtual { " " } else { &self.space }; let space = if is_virtual { " " } else { &self.space };
let nbsp = if is_virtual { " " } else { &self.nbsp }; let nbsp = if is_virtual { " " } else { &self.nbsp };
let nnbsp = if is_virtual { " " } else { &self.nnbsp };
let tab = if is_virtual { let tab = if is_virtual {
&self.virtual_tab &self.virtual_tab
} else { } else {
@ -461,6 +469,7 @@ impl<'a> TextRenderer<'a> {
// TODO special rendering for other whitespaces? // TODO special rendering for other whitespaces?
Grapheme::Other { ref g } if g == " " => space, Grapheme::Other { ref g } if g == " " => space,
Grapheme::Other { ref g } if g == "\u{00A0}" => nbsp, Grapheme::Other { ref g } if g == "\u{00A0}" => nbsp,
Grapheme::Other { ref g } if g == "\u{202F}" => nnbsp,
Grapheme::Other { ref g } => g, Grapheme::Other { ref g } => g,
Grapheme::Newline => &self.newline, Grapheme::Newline => &self.newline,
}; };

View file

@ -4,6 +4,7 @@ use helix_core::unicode::width::UnicodeWidthStr;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
const NBSP: &str = "\u{00a0}"; const NBSP: &str = "\u{00a0}";
const NNBSP: &str = "\u{202f}";
/// A state machine to pack styled symbols into lines. /// A state machine to pack styled symbols into lines.
/// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming /// Cannot implement it as Iterator since it yields slices of the internal buffer (need streaming
@ -58,7 +59,8 @@ impl<'a, 'b> LineComposer<'a> for WordWrapper<'a, 'b> {
let mut symbols_exhausted = true; let mut symbols_exhausted = true;
for StyledGrapheme { symbol, style } in &mut self.symbols { for StyledGrapheme { symbol, style } in &mut self.symbols {
symbols_exhausted = false; 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. // Ignore characters wider that the total max width.
if symbol.width() as u16 > self.max_line_width if symbol.width() as u16 > self.max_line_width
@ -496,6 +498,20 @@ mod test {
assert_eq!(word_wrapper_space, vec!["AAAAAAAAAAAAAAA AAAA", "AAA",]); 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] #[test]
fn line_composer_word_wrapper_preserve_indentation() { fn line_composer_word_wrapper_preserve_indentation() {
let width = 20; let width = 20;

View file

@ -702,6 +702,7 @@ pub enum WhitespaceRender {
default: Option<WhitespaceRenderValue>, default: Option<WhitespaceRenderValue>,
space: Option<WhitespaceRenderValue>, space: Option<WhitespaceRenderValue>,
nbsp: Option<WhitespaceRenderValue>, nbsp: Option<WhitespaceRenderValue>,
nnbsp: Option<WhitespaceRenderValue>,
tab: Option<WhitespaceRenderValue>, tab: Option<WhitespaceRenderValue>,
newline: Option<WhitespaceRenderValue>, newline: Option<WhitespaceRenderValue>,
}, },
@ -733,6 +734,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 { pub fn tab(&self) -> WhitespaceRenderValue {
match *self { match *self {
Self::Basic(val) => val, Self::Basic(val) => val,
@ -756,6 +765,7 @@ impl WhitespaceRender {
pub struct WhitespaceCharacters { pub struct WhitespaceCharacters {
pub space: char, pub space: char,
pub nbsp: char, pub nbsp: char,
pub nnbsp: char,
pub tab: char, pub tab: char,
pub tabpad: char, pub tabpad: char,
pub newline: char, pub newline: char,
@ -766,6 +776,7 @@ impl Default for WhitespaceCharacters {
Self { Self {
space: '·', // U+00B7 space: '·', // U+00B7
nbsp: '', // U+237D nbsp: '', // U+237D
nnbsp: '', // U+2423
tab: '', // U+2192 tab: '', // U+2192
newline: '', // U+23CE newline: '', // U+23CE
tabpad: ' ', tabpad: ' ',