Don't apply contrast adjustment to decorative chars (#34238)

Richard Feldman created

Closes #34234

Release Notes:

- Automatic contrast adjustment in terminal is no longer applied to
decorative characters used in block art.

Change summary

crates/terminal_view/src/terminal_element.rs | 106 +++++++++++++++++++++
1 file changed, 105 insertions(+), 1 deletion(-)

Detailed changes

crates/terminal_view/src/terminal_element.rs 🔗

@@ -494,6 +494,22 @@ impl TerminalElement {
         }
     }
 
+    /// Checks if a character is a decorative block/box-like character that should
+    /// preserve its exact colors without contrast adjustment.
+    ///
+    /// Fixes https://github.com/zed-industries/zed/issues/34234 - we can
+    /// expand this list if we run into more similar cases, but the goal
+    /// is to be conservative here.
+    fn is_decorative_character(ch: char) -> bool {
+        matches!(
+            ch as u32,
+            // 0x2500..=0x257F Box Drawing
+            // 0x2580..=0x259F Block Elements
+            // 0x25A0..=0x25D7 Geometric Shapes (block/box-like subset)
+            0x2500..=0x25D7
+        )
+    }
+
     /// Converts the Alacritty cell styles to GPUI text styles and background color.
     fn cell_style(
         indexed: &IndexedCell,
@@ -508,7 +524,10 @@ impl TerminalElement {
         let mut fg = convert_color(&fg, colors);
         let bg = convert_color(&bg, colors);
 
-        fg = color_contrast::ensure_minimum_contrast(fg, bg, minimum_contrast);
+        // Only apply contrast adjustment to non-decorative characters
+        if !Self::is_decorative_character(indexed.c) {
+            fg = color_contrast::ensure_minimum_contrast(fg, bg, minimum_contrast);
+        }
 
         // Ghostty uses (175/255) as the multiplier (~0.69), Alacritty uses 0.66, Kitty
         // uses 0.75. We're using 0.7 because it's pretty well in the middle of that.
@@ -1575,6 +1594,91 @@ mod tests {
     use super::*;
     use gpui::{AbsoluteLength, Hsla, font};
 
+    #[test]
+    fn test_is_decorative_character() {
+        // Box Drawing characters (U+2500 to U+257F)
+        assert!(TerminalElement::is_decorative_character('─')); // U+2500
+        assert!(TerminalElement::is_decorative_character('│')); // U+2502
+        assert!(TerminalElement::is_decorative_character('┌')); // U+250C
+        assert!(TerminalElement::is_decorative_character('┐')); // U+2510
+        assert!(TerminalElement::is_decorative_character('└')); // U+2514
+        assert!(TerminalElement::is_decorative_character('┘')); // U+2518
+        assert!(TerminalElement::is_decorative_character('┼')); // U+253C
+
+        // Block Elements (U+2580 to U+259F)
+        assert!(TerminalElement::is_decorative_character('▀')); // U+2580
+        assert!(TerminalElement::is_decorative_character('▄')); // U+2584
+        assert!(TerminalElement::is_decorative_character('█')); // U+2588
+        assert!(TerminalElement::is_decorative_character('░')); // U+2591
+        assert!(TerminalElement::is_decorative_character('▒')); // U+2592
+        assert!(TerminalElement::is_decorative_character('▓')); // U+2593
+
+        // Geometric Shapes - block/box-like subset (U+25A0 to U+25D7)
+        assert!(TerminalElement::is_decorative_character('■')); // U+25A0
+        assert!(TerminalElement::is_decorative_character('□')); // U+25A1
+        assert!(TerminalElement::is_decorative_character('▲')); // U+25B2
+        assert!(TerminalElement::is_decorative_character('▼')); // U+25BC
+        assert!(TerminalElement::is_decorative_character('◆')); // U+25C6
+        assert!(TerminalElement::is_decorative_character('●')); // U+25CF
+
+        // The specific character from the issue
+        assert!(TerminalElement::is_decorative_character('◗')); // U+25D7
+
+        // Characters that should NOT be considered decorative
+        assert!(!TerminalElement::is_decorative_character('A'));
+        assert!(!TerminalElement::is_decorative_character('a'));
+        assert!(!TerminalElement::is_decorative_character('0'));
+        assert!(!TerminalElement::is_decorative_character(' '));
+        assert!(!TerminalElement::is_decorative_character('←')); // U+2190 (Arrow, not in our ranges)
+        assert!(!TerminalElement::is_decorative_character('→')); // U+2192 (Arrow, not in our ranges)
+        assert!(!TerminalElement::is_decorative_character('◘')); // U+25D8 (Just outside our range)
+        assert!(!TerminalElement::is_decorative_character('◙')); // U+25D9 (Just outside our range)
+    }
+
+    #[test]
+    fn test_decorative_character_boundary_cases() {
+        // Test exact boundaries of our ranges
+        // Box Drawing range boundaries
+        assert!(TerminalElement::is_decorative_character('\u{2500}')); // First char
+        assert!(TerminalElement::is_decorative_character('\u{257F}')); // Last char
+        assert!(!TerminalElement::is_decorative_character('\u{24FF}')); // Just before
+
+        // Block Elements range boundaries
+        assert!(TerminalElement::is_decorative_character('\u{2580}')); // First char
+        assert!(TerminalElement::is_decorative_character('\u{259F}')); // Last char
+
+        // Geometric Shapes subset boundaries
+        assert!(TerminalElement::is_decorative_character('\u{25A0}')); // First char
+        assert!(TerminalElement::is_decorative_character('\u{25D7}')); // Last char (◗)
+        assert!(!TerminalElement::is_decorative_character('\u{25D8}')); // Just after
+    }
+
+    #[test]
+    fn test_decorative_characters_bypass_contrast_adjustment() {
+        // Decorative characters should not be affected by contrast adjustment
+
+        // The specific character from issue #34234
+        let problematic_char = '◗'; // U+25D7
+        assert!(
+            TerminalElement::is_decorative_character(problematic_char),
+            "Character ◗ (U+25D7) should be recognized as decorative"
+        );
+
+        // Verify some other commonly used decorative characters
+        assert!(TerminalElement::is_decorative_character('│')); // Vertical line
+        assert!(TerminalElement::is_decorative_character('─')); // Horizontal line
+        assert!(TerminalElement::is_decorative_character('█')); // Full block
+        assert!(TerminalElement::is_decorative_character('▓')); // Dark shade
+        assert!(TerminalElement::is_decorative_character('■')); // Black square
+        assert!(TerminalElement::is_decorative_character('●')); // Black circle
+
+        // Verify normal text characters are NOT decorative
+        assert!(!TerminalElement::is_decorative_character('A'));
+        assert!(!TerminalElement::is_decorative_character('1'));
+        assert!(!TerminalElement::is_decorative_character('$'));
+        assert!(!TerminalElement::is_decorative_character(' '));
+    }
+
     #[test]
     fn test_contrast_adjustment_logic() {
         // Test the core contrast adjustment logic without needing full app context