mirror of
https://github.com/wez/wezterm.git
synced 2024-12-24 13:52:55 +03:00
termwiz: better fidelity Emoji_Presentation logic
Make a distinction between default and selected presentation, and account for that in the cell width. Add a method to the cell that returns the effective presentation. refs: https://github.com/wez/wezterm/issues/997
This commit is contained in:
parent
e3acbd594f
commit
28e8b5ff2f
1261
termwiz/data/emoji-data.txt
Normal file
1261
termwiz/data/emoji-data.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
||||
//! Model a cell in the terminal display
|
||||
use crate::color::{ColorAttribute, PaletteIndex};
|
||||
pub use crate::emoji::Presentation;
|
||||
pub use crate::escape::osc::Hyperlink;
|
||||
use crate::image::ImageCell;
|
||||
#[cfg(feature = "use_serde")]
|
||||
@ -647,6 +648,16 @@ impl Cell {
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates whether this cell has text or emoji presentation.
|
||||
/// The width already reflects that choice; this information
|
||||
/// is also useful when selecting an appropriate font.
|
||||
pub fn presentation(&self) -> Presentation {
|
||||
match Presentation::for_grapheme(self.str()) {
|
||||
(_, Some(variation)) => variation,
|
||||
(presentation, None) => presentation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new cell holding the specified grapheme.
|
||||
/// The grapheme is passed as a string slice and is intended to hold
|
||||
/// double-width characters, or combining unicode sequences, that need
|
||||
@ -700,46 +711,11 @@ pub fn unicode_column_width(s: &str) -> usize {
|
||||
/// Returns the number of cells visually occupied by a grapheme.
|
||||
/// The input string must be a single grapheme.
|
||||
pub fn grapheme_column_width(s: &str) -> usize {
|
||||
// Due to this issue:
|
||||
// https://github.com/unicode-rs/unicode-width/issues/4
|
||||
// we cannot simply use the unicode-width crate to compute
|
||||
// the desired value.
|
||||
// Let's check for emoji-ness for ourselves first
|
||||
use xi_unicode::EmojiExt;
|
||||
let mut emoji = false;
|
||||
let mut implied_emoji_presentation = false;
|
||||
|
||||
for c in s.chars() {
|
||||
if c == '\u{FE0F}' {
|
||||
// Explicit emoji presentation
|
||||
return 2;
|
||||
} else if c == '\u{FE0E}' {
|
||||
// Explicit text presentation
|
||||
return 1;
|
||||
} else if c.is_emoji_modifier_base() || c.is_emoji_modifier() {
|
||||
// We'll probably use emoji presentation for this,
|
||||
// but defer the decision until we've had a chance
|
||||
// to look for an explicit presentation selection.
|
||||
implied_emoji_presentation = true;
|
||||
} else if c.is_emoji() {
|
||||
emoji = true;
|
||||
}
|
||||
}
|
||||
|
||||
if implied_emoji_presentation {
|
||||
return 2;
|
||||
}
|
||||
|
||||
let width = UnicodeWidthStr::width(s);
|
||||
if emoji {
|
||||
// For sequences such as "deaf man", UnicodeWidthStr::width()
|
||||
// returns 3 because of the widths of the component glyphs,
|
||||
// rather than 2 for a single double width grapheme.
|
||||
// If we saw any emoji within the characters then we assume
|
||||
// that it can be a maximum of 2 cells in width.
|
||||
width.min(2)
|
||||
} else {
|
||||
width
|
||||
match Presentation::for_grapheme(s) {
|
||||
(_, Some(Presentation::Emoji)) => 2,
|
||||
(_, Some(Presentation::Text)) => 1,
|
||||
(Presentation::Emoji, None) => 2,
|
||||
(Presentation::Text, None) => UnicodeWidthStr::width(s).min(2),
|
||||
}
|
||||
}
|
||||
|
||||
@ -852,21 +828,21 @@ mod test {
|
||||
#[test]
|
||||
fn issue_997() {
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
let waving_hand = "\u{270c}";
|
||||
let waving_hand_text_presentation = "\u{270c}\u{fe0e}";
|
||||
let victory_hand = "\u{270c}";
|
||||
let victory_hand_text_presentation = "\u{270c}\u{fe0e}";
|
||||
|
||||
assert_eq!(unicode_column_width(waving_hand_text_presentation), 1);
|
||||
assert_eq!(unicode_column_width(waving_hand), 2);
|
||||
assert_eq!(unicode_column_width(victory_hand_text_presentation), 1);
|
||||
assert_eq!(unicode_column_width(victory_hand), 1);
|
||||
|
||||
assert_eq!(
|
||||
waving_hand_text_presentation
|
||||
victory_hand_text_presentation
|
||||
.graphemes(true)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![waving_hand_text_presentation.to_string()]
|
||||
vec![victory_hand_text_presentation.to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
waving_hand.graphemes(true).collect::<Vec<_>>(),
|
||||
vec![waving_hand.to_string()]
|
||||
victory_hand.graphemes(true).collect::<Vec<_>>(),
|
||||
vec![victory_hand.to_string()]
|
||||
);
|
||||
|
||||
let copyright_emoji_presentation = "\u{00A9}\u{FE0F}";
|
||||
@ -886,5 +862,27 @@ mod test {
|
||||
vec![copyright_text_presentation.to_string()]
|
||||
);
|
||||
assert_eq!(unicode_column_width(copyright_text_presentation), 1);
|
||||
|
||||
let raised_fist = "\u{270a}";
|
||||
let raised_fist_text = "\u{270a}\u{fe0e}";
|
||||
assert_eq!(
|
||||
Presentation::for_grapheme(raised_fist),
|
||||
(Presentation::Emoji, None)
|
||||
);
|
||||
assert_eq!(unicode_column_width(raised_fist), 2);
|
||||
assert_eq!(
|
||||
Presentation::for_grapheme(raised_fist_text),
|
||||
(Presentation::Emoji, Some(Presentation::Text))
|
||||
);
|
||||
assert_eq!(unicode_column_width(raised_fist_text), 1);
|
||||
|
||||
assert_eq!(
|
||||
raised_fist_text.graphemes(true).collect::<Vec<_>>(),
|
||||
vec![raised_fist_text.to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
raised_fist.graphemes(true).collect::<Vec<_>>(),
|
||||
vec![raised_fist.to_string()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
299
termwiz/src/emoji.rs
Normal file
299
termwiz/src/emoji.rs
Normal file
@ -0,0 +1,299 @@
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Presentation {
|
||||
Text,
|
||||
Emoji,
|
||||
}
|
||||
|
||||
impl Presentation {
|
||||
/// Returns the default presentation followed
|
||||
/// by the explicit presentation if specified
|
||||
/// by a variation selector
|
||||
pub fn for_grapheme(s: &str) -> (Self, Option<Self>) {
|
||||
let mut presentation = Self::Text;
|
||||
let mut variation = None;
|
||||
for c in s.chars() {
|
||||
if c == '\u{FE0F}' {
|
||||
// Explicit emoji presentation
|
||||
variation.replace(Self::Emoji);
|
||||
} else if c == '\u{FE0E}' {
|
||||
variation.replace(Self::Text);
|
||||
} else if Self::for_char(c) == Self::Emoji {
|
||||
presentation = Self::Emoji;
|
||||
}
|
||||
// Note that `c` may be some other combining
|
||||
// sequence that doesn't definitively indicate
|
||||
// that we're text, so we only positively
|
||||
// change presentation when we identify an
|
||||
// emoji char.
|
||||
}
|
||||
(presentation, variation)
|
||||
}
|
||||
|
||||
pub fn for_char(c: char) -> Self {
|
||||
// Matches Emoji_Presentation properties from Unicode 13.0
|
||||
match c {
|
||||
'\u{231A}'..='\u{231B}'
|
||||
| '\u{23E9}'..='\u{23EC}'
|
||||
| '\u{23F0}'
|
||||
| '\u{23F3}'
|
||||
| '\u{25FD}'..='\u{25FE}'
|
||||
| '\u{2614}'..='\u{2615}'
|
||||
| '\u{2648}'..='\u{2653}'
|
||||
| '\u{267F}'
|
||||
| '\u{2693}'
|
||||
| '\u{26A1}'
|
||||
| '\u{26AA}'..='\u{26AB}'
|
||||
| '\u{26BD}'..='\u{26BE}'
|
||||
| '\u{26C4}'..='\u{26C5}'
|
||||
| '\u{26CE}'
|
||||
| '\u{26D4}'
|
||||
| '\u{26EA}'
|
||||
| '\u{26F2}'..='\u{26F3}'
|
||||
| '\u{26F5}'
|
||||
| '\u{26FA}'
|
||||
| '\u{26FD}'
|
||||
| '\u{2705}'
|
||||
| '\u{270A}'..='\u{270B}'
|
||||
| '\u{2728}'
|
||||
| '\u{274C}'
|
||||
| '\u{274E}'
|
||||
| '\u{2753}'..='\u{2755}'
|
||||
| '\u{2757}'
|
||||
| '\u{2795}'..='\u{2797}'
|
||||
| '\u{27B0}'
|
||||
| '\u{27BF}'
|
||||
| '\u{2B1B}'..='\u{2B1C}'
|
||||
| '\u{2B50}'
|
||||
| '\u{2B55}'
|
||||
| '\u{1F004}'
|
||||
| '\u{1F0CF}'
|
||||
| '\u{1F18E}'
|
||||
| '\u{1F191}'..='\u{1F19A}'
|
||||
| '\u{1F1E6}'..='\u{1F1FF}'
|
||||
| '\u{1F201}'
|
||||
| '\u{1F21A}'
|
||||
| '\u{1F22F}'
|
||||
| '\u{1F232}'..='\u{1F236}'
|
||||
| '\u{1F238}'..='\u{1F23A}'
|
||||
| '\u{1F250}'..='\u{1F251}'
|
||||
| '\u{1F300}'..='\u{1F30C}'
|
||||
| '\u{1F30D}'..='\u{1F30E}'
|
||||
| '\u{1F30F}'
|
||||
| '\u{1F310}'
|
||||
| '\u{1F311}'
|
||||
| '\u{1F312}'
|
||||
| '\u{1F313}'..='\u{1F315}'
|
||||
| '\u{1F316}'..='\u{1F318}'
|
||||
| '\u{1F319}'
|
||||
| '\u{1F31A}'
|
||||
| '\u{1F31B}'
|
||||
| '\u{1F31C}'
|
||||
| '\u{1F31D}'..='\u{1F31E}'
|
||||
| '\u{1F31F}'..='\u{1F320}'
|
||||
| '\u{1F32D}'..='\u{1F32F}'
|
||||
| '\u{1F330}'..='\u{1F331}'
|
||||
| '\u{1F332}'..='\u{1F333}'
|
||||
| '\u{1F334}'..='\u{1F335}'
|
||||
| '\u{1F337}'..='\u{1F34A}'
|
||||
| '\u{1F34B}'
|
||||
| '\u{1F34C}'..='\u{1F34F}'
|
||||
| '\u{1F350}'
|
||||
| '\u{1F351}'..='\u{1F37B}'
|
||||
| '\u{1F37C}'
|
||||
| '\u{1F37E}'..='\u{1F37F}'
|
||||
| '\u{1F380}'..='\u{1F393}'
|
||||
| '\u{1F3A0}'..='\u{1F3C4}'
|
||||
| '\u{1F3C5}'
|
||||
| '\u{1F3C6}'
|
||||
| '\u{1F3C7}'
|
||||
| '\u{1F3C8}'
|
||||
| '\u{1F3C9}'
|
||||
| '\u{1F3CA}'
|
||||
| '\u{1F3CF}'..='\u{1F3D3}'
|
||||
| '\u{1F3E0}'..='\u{1F3E3}'
|
||||
| '\u{1F3E4}'
|
||||
| '\u{1F3E5}'..='\u{1F3F0}'
|
||||
| '\u{1F3F4}'
|
||||
| '\u{1F3F8}'..='\u{1F407}'
|
||||
| '\u{1F408}'
|
||||
| '\u{1F409}'..='\u{1F40B}'
|
||||
| '\u{1F40C}'..='\u{1F40E}'
|
||||
| '\u{1F40F}'..='\u{1F410}'
|
||||
| '\u{1F411}'..='\u{1F412}'
|
||||
| '\u{1F413}'
|
||||
| '\u{1F414}'
|
||||
| '\u{1F415}'
|
||||
| '\u{1F416}'
|
||||
| '\u{1F417}'..='\u{1F429}'
|
||||
| '\u{1F42A}'
|
||||
| '\u{1F42B}'..='\u{1F43E}'
|
||||
| '\u{1F440}'
|
||||
| '\u{1F442}'..='\u{1F464}'
|
||||
| '\u{1F465}'
|
||||
| '\u{1F466}'..='\u{1F46B}'
|
||||
| '\u{1F46C}'..='\u{1F46D}'
|
||||
| '\u{1F46E}'..='\u{1F4AC}'
|
||||
| '\u{1F4AD}'
|
||||
| '\u{1F4AE}'..='\u{1F4B5}'
|
||||
| '\u{1F4B6}'..='\u{1F4B7}'
|
||||
| '\u{1F4B8}'..='\u{1F4EB}'
|
||||
| '\u{1F4EC}'..='\u{1F4ED}'
|
||||
| '\u{1F4EE}'
|
||||
| '\u{1F4EF}'
|
||||
| '\u{1F4F0}'..='\u{1F4F4}'
|
||||
| '\u{1F4F5}'
|
||||
| '\u{1F4F6}'..='\u{1F4F7}'
|
||||
| '\u{1F4F8}'
|
||||
| '\u{1F4F9}'..='\u{1F4FC}'
|
||||
| '\u{1F4FF}'..='\u{1F502}'
|
||||
| '\u{1F503}'
|
||||
| '\u{1F504}'..='\u{1F507}'
|
||||
| '\u{1F508}'
|
||||
| '\u{1F509}'
|
||||
| '\u{1F50A}'..='\u{1F514}'
|
||||
| '\u{1F515}'
|
||||
| '\u{1F516}'..='\u{1F52B}'
|
||||
| '\u{1F52C}'..='\u{1F52D}'
|
||||
| '\u{1F52E}'..='\u{1F53D}'
|
||||
| '\u{1F54B}'..='\u{1F54E}'
|
||||
| '\u{1F550}'..='\u{1F55B}'
|
||||
| '\u{1F55C}'..='\u{1F567}'
|
||||
| '\u{1F57A}'
|
||||
| '\u{1F595}'..='\u{1F596}'
|
||||
| '\u{1F5A4}'
|
||||
| '\u{1F5FB}'..='\u{1F5FF}'
|
||||
| '\u{1F600}'
|
||||
| '\u{1F601}'..='\u{1F606}'
|
||||
| '\u{1F607}'..='\u{1F608}'
|
||||
| '\u{1F609}'..='\u{1F60D}'
|
||||
| '\u{1F60E}'
|
||||
| '\u{1F60F}'
|
||||
| '\u{1F610}'
|
||||
| '\u{1F611}'
|
||||
| '\u{1F612}'..='\u{1F614}'
|
||||
| '\u{1F615}'
|
||||
| '\u{1F616}'
|
||||
| '\u{1F617}'
|
||||
| '\u{1F618}'
|
||||
| '\u{1F619}'
|
||||
| '\u{1F61A}'
|
||||
| '\u{1F61B}'
|
||||
| '\u{1F61C}'..='\u{1F61E}'
|
||||
| '\u{1F61F}'
|
||||
| '\u{1F620}'..='\u{1F625}'
|
||||
| '\u{1F626}'..='\u{1F627}'
|
||||
| '\u{1F628}'..='\u{1F62B}'
|
||||
| '\u{1F62C}'
|
||||
| '\u{1F62D}'
|
||||
| '\u{1F62E}'..='\u{1F62F}'
|
||||
| '\u{1F630}'..='\u{1F633}'
|
||||
| '\u{1F634}'
|
||||
| '\u{1F635}'
|
||||
| '\u{1F636}'
|
||||
| '\u{1F637}'..='\u{1F640}'
|
||||
| '\u{1F641}'..='\u{1F644}'
|
||||
| '\u{1F645}'..='\u{1F64F}'
|
||||
| '\u{1F680}'
|
||||
| '\u{1F681}'..='\u{1F682}'
|
||||
| '\u{1F683}'..='\u{1F685}'
|
||||
| '\u{1F686}'
|
||||
| '\u{1F687}'
|
||||
| '\u{1F688}'
|
||||
| '\u{1F689}'
|
||||
| '\u{1F68A}'..='\u{1F68B}'
|
||||
| '\u{1F68C}'
|
||||
| '\u{1F68D}'
|
||||
| '\u{1F68E}'
|
||||
| '\u{1F68F}'
|
||||
| '\u{1F690}'
|
||||
| '\u{1F691}'..='\u{1F693}'
|
||||
| '\u{1F694}'
|
||||
| '\u{1F695}'
|
||||
| '\u{1F696}'
|
||||
| '\u{1F697}'
|
||||
| '\u{1F698}'
|
||||
| '\u{1F699}'..='\u{1F69A}'
|
||||
| '\u{1F69B}'..='\u{1F6A1}'
|
||||
| '\u{1F6A2}'
|
||||
| '\u{1F6A3}'
|
||||
| '\u{1F6A4}'..='\u{1F6A5}'
|
||||
| '\u{1F6A6}'
|
||||
| '\u{1F6A7}'..='\u{1F6AD}'
|
||||
| '\u{1F6AE}'..='\u{1F6B1}'
|
||||
| '\u{1F6B2}'
|
||||
| '\u{1F6B3}'..='\u{1F6B5}'
|
||||
| '\u{1F6B6}'
|
||||
| '\u{1F6B7}'..='\u{1F6B8}'
|
||||
| '\u{1F6B9}'..='\u{1F6BE}'
|
||||
| '\u{1F6BF}'
|
||||
| '\u{1F6C0}'
|
||||
| '\u{1F6C1}'..='\u{1F6C5}'
|
||||
| '\u{1F6CC}'
|
||||
| '\u{1F6D0}'
|
||||
| '\u{1F6D1}'..='\u{1F6D2}'
|
||||
| '\u{1F6D5}'
|
||||
| '\u{1F6D6}'..='\u{1F6D7}'
|
||||
| '\u{1F6EB}'..='\u{1F6EC}'
|
||||
| '\u{1F6F4}'..='\u{1F6F6}'
|
||||
| '\u{1F6F7}'..='\u{1F6F8}'
|
||||
| '\u{1F6F9}'
|
||||
| '\u{1F6FA}'
|
||||
| '\u{1F6FB}'..='\u{1F6FC}'
|
||||
| '\u{1F7E0}'..='\u{1F7EB}'
|
||||
| '\u{1F90C}'
|
||||
| '\u{1F90D}'..='\u{1F90F}'
|
||||
| '\u{1F910}'..='\u{1F918}'
|
||||
| '\u{1F919}'..='\u{1F91E}'
|
||||
| '\u{1F91F}'
|
||||
| '\u{1F920}'..='\u{1F927}'
|
||||
| '\u{1F928}'..='\u{1F92F}'
|
||||
| '\u{1F930}'
|
||||
| '\u{1F931}'..='\u{1F932}'
|
||||
| '\u{1F933}'..='\u{1F93A}'
|
||||
| '\u{1F93C}'..='\u{1F93E}'
|
||||
| '\u{1F93F}'
|
||||
| '\u{1F940}'..='\u{1F945}'
|
||||
| '\u{1F947}'..='\u{1F94B}'
|
||||
| '\u{1F94C}'
|
||||
| '\u{1F94D}'..='\u{1F94F}'
|
||||
| '\u{1F950}'..='\u{1F95E}'
|
||||
| '\u{1F95F}'..='\u{1F96B}'
|
||||
| '\u{1F96C}'..='\u{1F970}'
|
||||
| '\u{1F971}'
|
||||
| '\u{1F972}'
|
||||
| '\u{1F973}'..='\u{1F976}'
|
||||
| '\u{1F977}'..='\u{1F978}'
|
||||
| '\u{1F97A}'
|
||||
| '\u{1F97B}'
|
||||
| '\u{1F97C}'..='\u{1F97F}'
|
||||
| '\u{1F980}'..='\u{1F984}'
|
||||
| '\u{1F985}'..='\u{1F991}'
|
||||
| '\u{1F992}'..='\u{1F997}'
|
||||
| '\u{1F998}'..='\u{1F9A2}'
|
||||
| '\u{1F9A3}'..='\u{1F9A4}'
|
||||
| '\u{1F9A5}'..='\u{1F9AA}'
|
||||
| '\u{1F9AB}'..='\u{1F9AD}'
|
||||
| '\u{1F9AE}'..='\u{1F9AF}'
|
||||
| '\u{1F9B0}'..='\u{1F9B9}'
|
||||
| '\u{1F9BA}'..='\u{1F9BF}'
|
||||
| '\u{1F9C0}'
|
||||
| '\u{1F9C1}'..='\u{1F9C2}'
|
||||
| '\u{1F9C3}'..='\u{1F9CA}'
|
||||
| '\u{1F9CB}'
|
||||
| '\u{1F9CD}'..='\u{1F9CF}'
|
||||
| '\u{1F9D0}'..='\u{1F9E6}'
|
||||
| '\u{1F9E7}'..='\u{1F9FF}'
|
||||
| '\u{1FA70}'..='\u{1FA73}'
|
||||
| '\u{1FA74}'
|
||||
| '\u{1FA78}'..='\u{1FA7A}'
|
||||
| '\u{1FA80}'..='\u{1FA82}'
|
||||
| '\u{1FA83}'..='\u{1FA86}'
|
||||
| '\u{1FA90}'..='\u{1FA95}'
|
||||
| '\u{1FA96}'..='\u{1FAA8}'
|
||||
| '\u{1FAB0}'..='\u{1FAB6}'
|
||||
| '\u{1FAC0}'..='\u{1FAC2}'
|
||||
| '\u{1FAD0}'..='\u{1FAD6}' => Self::Emoji,
|
||||
_ => Self::Text,
|
||||
}
|
||||
}
|
||||
}
|
@ -40,6 +40,8 @@
|
||||
//! * `widgets` - enables the widget layout and related traits
|
||||
//! * `use_serde` - makes a number of structs serde serializable
|
||||
|
||||
mod emoji;
|
||||
|
||||
pub mod caps;
|
||||
pub mod cell;
|
||||
pub mod cellcluster;
|
||||
|
Loading…
Reference in New Issue
Block a user