This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 9730682eb7d4 CAMEL-23830: Make TUI shell ANSI colors readable on dark 
terminals
9730682eb7d4 is described below

commit 9730682eb7d4f5aefff0a639b26e7d64b95cb5ab
Author: Adriano Machado <[email protected]>
AuthorDate: Thu Jun 25 00:54:28 2026 -0400

    CAMEL-23830: Make TUI shell ANSI colors readable on dark terminals
    
    Map JLine's 12-bit encoded ANSI palette colors to terminal-themed
    Color.ansi() values instead of literal RGB, so standard colors like
    red are rendered using the host terminal's theme rather than a fixed
    dim value that is hard to read on dark backgrounds. True-color cells
    such as the orange shell prompt keep their literal RGB value.
    
    Closes #24237
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ShellPanel.java    | 61 +++++++++++++++---
 .../core/commands/tui/ShellPanelColorTest.java     | 75 ++++++++++++++++++++++
 2 files changed, 126 insertions(+), 10 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanel.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanel.java
index 21da1a341e54..b4f43e06639e 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanel.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanel.java
@@ -26,6 +26,7 @@ import java.util.Collections;
 import java.util.List;
 
 import dev.tamboui.layout.Rect;
+import dev.tamboui.style.AnsiColor;
 import dev.tamboui.style.Color;
 import dev.tamboui.style.Overflow;
 import dev.tamboui.style.Style;
@@ -506,25 +507,65 @@ class ShellPanel {
 
         // Foreground color (if set)
         if ((y & 0x1) != 0) {
-            int fg = (int) ((attr >> 12) & 0xFFF);
-            int r = ((fg >> 8) & 0xF) * 17;
-            int g = ((fg >> 4) & 0xF) * 17;
-            int b = (fg & 0xF) * 17;
-            style = style.fg(Color.rgb(r, g, b));
+            style = style.fg(resolveColor((int) ((attr >> 12) & 0xFFF)));
         }
 
         // Background color (if set)
         if ((y & 0x2) != 0) {
-            int bg = (int) (attr & 0xFFF);
-            int r = ((bg >> 8) & 0xF) * 17;
-            int g = ((bg >> 4) & 0xF) * 17;
-            int b = (bg & 0xF) * 17;
-            style = style.bg(Color.rgb(r, g, b));
+            style = style.bg(resolveColor((int) (attr & 0xFFF)));
         }
 
         return style;
     }
 
+    /**
+     * Converts a 12-bit (4-bit-per-channel) color value from the {@link 
ScreenTerminal} attribute word into a TamboUI
+     * {@link Color}.
+     * <p>
+     * When the value matches one of the 16 standard ANSI palette colors, a 
themed {@link Color#ansi(AnsiColor)} is
+     * returned so the host terminal applies its own scheme (keeping, for 
example, red error output and the command
+     * highlighter legible on dark backgrounds). Any other value is a 
true-color cell (such as the orange shell prompt)
+     * and is expanded to its literal RGB value.
+     */
+    static Color resolveColor(int rgb12) {
+        AnsiColor ansi = ansiColorFor(rgb12);
+        if (ansi != null) {
+            return Color.ansi(ansi);
+        }
+        // Expand each 4-bit channel back to 8 bits (0xN -> 0xNN, i.e. * 17).
+        int r = ((rgb12 >> 8) & 0xF) * 17;
+        int g = ((rgb12 >> 4) & 0xF) * 17;
+        int b = (rgb12 & 0xF) * 17;
+        return Color.rgb(r, g, b);
+    }
+
+    /**
+     * Maps a 12-bit color value to the standard ANSI palette color it 
encodes, or {@code null} if it does not match one
+     * of the 16 ANSI colors. The values are {@link ScreenTerminal}'s palette 
(xterm defaults) reduced to the top nibble
+     * of each channel, matching how {@code ScreenTerminal} stores them in the 
cell attribute.
+     */
+    static AnsiColor ansiColorFor(int rgb12) {
+        return switch (rgb12) {
+            case 0x000 -> AnsiColor.BLACK;
+            case 0x800 -> AnsiColor.RED;
+            case 0x080 -> AnsiColor.GREEN;
+            case 0x880 -> AnsiColor.YELLOW;
+            case 0x008 -> AnsiColor.BLUE;
+            case 0x808 -> AnsiColor.MAGENTA;
+            case 0x088 -> AnsiColor.CYAN;
+            case 0xccc -> AnsiColor.WHITE;
+            case 0x888 -> AnsiColor.BRIGHT_BLACK;
+            case 0xf00 -> AnsiColor.BRIGHT_RED;
+            case 0x0f0 -> AnsiColor.BRIGHT_GREEN;
+            case 0xff0 -> AnsiColor.BRIGHT_YELLOW;
+            case 0x00f -> AnsiColor.BRIGHT_BLUE;
+            case 0xf0f -> AnsiColor.BRIGHT_MAGENTA;
+            case 0x0ff -> AnsiColor.BRIGHT_CYAN;
+            case 0xfff -> AnsiColor.BRIGHT_WHITE;
+            default -> null;
+        };
+    }
+
     private static byte[] encodeKeyEvent(KeyEvent ke) {
         if (ke.code() == KeyCode.CHAR) {
             char ch = ke.character();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanelColorTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanelColorTest.java
new file mode 100644
index 000000000000..586fc5d36351
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ShellPanelColorTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.tui;
+
+import dev.tamboui.style.AnsiColor;
+import dev.tamboui.style.Color;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class ShellPanelColorTest {
+
+    // ScreenTerminal stores colors as the top nibble of each channel of its 
xterm palette. The 16 standard
+    // ANSI colors must be recognised and mapped to terminal-themed colors, 
otherwise (for example) ANSI red
+    // is reconstructed as a literal RGB(136,0,0) that is unreadable on dark 
backgrounds.
+
+    @Test
+    void detectsStandardAnsiColors() {
+        assertEquals(AnsiColor.BLACK, ShellPanel.ansiColorFor(0x000));
+        assertEquals(AnsiColor.RED, ShellPanel.ansiColorFor(0x800));
+        assertEquals(AnsiColor.GREEN, ShellPanel.ansiColorFor(0x080));
+        assertEquals(AnsiColor.YELLOW, ShellPanel.ansiColorFor(0x880));
+        assertEquals(AnsiColor.BLUE, ShellPanel.ansiColorFor(0x008));
+        assertEquals(AnsiColor.MAGENTA, ShellPanel.ansiColorFor(0x808));
+        assertEquals(AnsiColor.CYAN, ShellPanel.ansiColorFor(0x088));
+        assertEquals(AnsiColor.WHITE, ShellPanel.ansiColorFor(0xccc));
+    }
+
+    @Test
+    void detectsBrightAnsiColors() {
+        assertEquals(AnsiColor.BRIGHT_BLACK, ShellPanel.ansiColorFor(0x888));
+        assertEquals(AnsiColor.BRIGHT_RED, ShellPanel.ansiColorFor(0xf00));
+        assertEquals(AnsiColor.BRIGHT_GREEN, ShellPanel.ansiColorFor(0x0f0));
+        assertEquals(AnsiColor.BRIGHT_YELLOW, ShellPanel.ansiColorFor(0xff0));
+        assertEquals(AnsiColor.BRIGHT_BLUE, ShellPanel.ansiColorFor(0x00f));
+        assertEquals(AnsiColor.BRIGHT_MAGENTA, ShellPanel.ansiColorFor(0xf0f));
+        assertEquals(AnsiColor.BRIGHT_CYAN, ShellPanel.ansiColorFor(0x0ff));
+        assertEquals(AnsiColor.BRIGHT_WHITE, ShellPanel.ansiColorFor(0xfff));
+    }
+
+    @Test
+    void leavesTrueColorValuesUnmapped() {
+        // The shell prompt's orange (0xF69123) reduces to 0xf92, which is not 
a palette color and must stay true-color.
+        assertNull(ShellPanel.ansiColorFor(0xf92));
+        assertNull(ShellPanel.ansiColorFor(0x123));
+    }
+
+    @Test
+    void resolvesAnsiRedToThemedColorNotDimRgb() {
+        // The core of the bug fix: ANSI red must become a terminal-themed 
color, not the dim RGB(136,0,0)
+        // that the old code produced.
+        assertEquals(Color.ansi(AnsiColor.RED), 
ShellPanel.resolveColor(0x800));
+    }
+
+    @Test
+    void resolvesTrueColorToLiteralRgb() {
+        // A non-palette value keeps its literal colour, with each 4-bit 
channel expanded back to 8 bits (* 17).
+        assertEquals(Color.rgb(0xf * 17, 0x9 * 17, 0x2 * 17), 
ShellPanel.resolveColor(0xf92));
+    }
+}

Reply via email to