eza icon indicating copy to clipboard operation
eza copied to clipboard

bug: Multi-codepoint emojis fail silently in theme.yml glyph field

Open dcadenas opened this issue 2 months ago β€’ 0 comments

Bug Description

Emojis with Unicode variation selectors (like πŸ–ΌοΈ and πŸ–₯️) cause the entire theme.yml file to be silently ignored due to deserialization failure.

Reproduction Steps

  1. Create ~/.config/eza/theme.yml with:
filenames:
  data: {icon: {glyph: πŸ’Ύ}}        # Works - single codepoint
  Pictures: {icon: {glyph: πŸ–ΌοΈ}}    # Fails - has variation selector
  Desktop: {icon: {glyph: πŸ–₯️}}     # Fails - has variation selector
  1. Run eza -l --icons

Expected Behavior

All custom icons should display, including emojis with variation selectors.

Actual Behavior

  • The data icon (πŸ’Ύ) displays correctly
  • Theme parsing fails silently when it encounters πŸ–ΌοΈ or πŸ–₯️
  • All subsequent icons in theme.yml are ignored
  • User receives no error message

Root Cause

The glyph field in IconStyle and IconStyleOverride structs is defined as Option<char>:

src/theme/ui_styles.rs:18

pub struct IconStyle {
    pub glyph: Option<char>,  // ❌ Can only hold single Unicode scalar
    pub style: Option<Style>,
}

src/options/config.rs:210

pub struct IconStyleOverride {
    pub glyph: Option<char>,  // ❌ Same issue
    pub style: Option<StyleOverride>,
}

Rust's char type represents a single Unicode scalar value. However, many emojis consist of multiple codepoints:

Emoji Description Codepoints Status
πŸ–ΌοΈ Picture frame U+1F5BC + U+FE0F ❌ Fails
πŸ–₯️ Desktop computer U+1F5A5 + U+FE0F ❌ Fails
πŸ‘¨β€πŸ’» Man technologist U+1F468 + U+200D + U+1F4BB ❌ Fails
πŸ‡ΊπŸ‡Έ US flag U+1F1FA + U+1F1F8 ❌ Fails
πŸ’Ύ Floppy disk U+1F4BE βœ… Works

The second codepoint (U+FE0F) is a variation selector - an invisible modifier that tells the system to render the character as a colorful emoji.

Silent Failure

src/options/config.rs:617-621

pub fn to_theme(&self) -> Option<UiStyles> {
    let ui_styles_override: Option<UiStylesOverride> = {
        let file = std::fs::File::open(&self.location).ok()?;
        serde_norway::from_reader(&file).ok()  // ❌ .ok() swallows errors!
    };
    FromOverride::from(ui_styles_override, Some(UiStyles::default()))
}

The .ok() converts deserialization errors into None, causing the entire theme.yml to be ignored with no warning.

Affected Emojis

This affects:

  • βœ… Emojis with variation selectors (U+FE0F): πŸ–ΌοΈ πŸ–₯️ ⌚️ ☎️
  • βœ… ZWJ (Zero Width Joiner) sequences: πŸ‘¨β€πŸ’» πŸ‘©β€πŸŽ¨ πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦
  • βœ… Flag emojis (regional indicators): πŸ‡ΊπŸ‡Έ πŸ‡¬πŸ‡§ πŸ‡―πŸ‡΅
  • βœ… Skin tone modifiers: πŸ‘‹πŸ» πŸ‘‹πŸΏ
  • βœ… Any other multi-codepoint emoji

Environment

  • eza version: v0.23.4
  • OS: Linux (affects all platforms)
  • Shell: Any
  • Terminal: Any

Proposed Solution

  1. Change glyph field from Option<char> to Option<String> to support grapheme clusters
  2. Add proper error handling to report deserialization failures instead of silently ignoring them

This would enable full emoji support while maintaining backward compatibility with existing single-character icons.

dcadenas avatar Oct 27 '25 18:10 dcadenas