This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24793 in repository https://gitbox.apache.org/repos/asf/camel.git
commit c4bd262b15f8dea4724ed4d42ff216ecba3e979a Author: Claus Ibsen <[email protected]> AuthorDate: Fri Oct 2 19:04:55 2026 +0200 CAMEL-24793: camel-jbang - themes ported from editor color schemes use the scheme's own code colors Dracula, Nord, Solarized, Gruvbox, Catppuccin, Tokyo Night, Rose Pine, Kanagawa and Everforest now define the optional syntax-* tokens from their scheme, with colors that fall below a 3:1 contrast on the background lifted slightly. ThemeSyntaxPaletteTest guards contrast and distinct keyword/string/comment colors for every theme, including the Monokai/GitHub fallback. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../images/jbang/camel-tui-theme-nord-source.png | Bin 0 -> 257363 bytes .../ROOT/pages/camel-jbang-tui-settings.adoc | 13 +- .../resources/tui/themes/catppuccin-frappe.tcss | 11 ++ .../resources/tui/themes/catppuccin-latte.tcss | 12 ++ .../resources/tui/themes/catppuccin-mocha.tcss | 11 ++ .../src/main/resources/tui/themes/dracula.tcss | 11 ++ .../resources/tui/themes/everforest-light.tcss | 12 ++ .../src/main/resources/tui/themes/everforest.tcss | 11 ++ .../main/resources/tui/themes/gruvbox-dark.tcss | 11 ++ .../main/resources/tui/themes/gruvbox-light.tcss | 11 ++ .../src/main/resources/tui/themes/kanagawa.tcss | 11 ++ .../src/main/resources/tui/themes/nord-light.tcss | 11 ++ .../src/main/resources/tui/themes/nord.tcss | 12 ++ .../main/resources/tui/themes/rose-pine-moon.tcss | 11 ++ .../src/main/resources/tui/themes/rose-pine.tcss | 11 ++ .../main/resources/tui/themes/solarized-dark.tcss | 12 ++ .../main/resources/tui/themes/solarized-light.tcss | 12 ++ .../src/main/resources/tui/themes/theme.md | 14 ++ .../src/main/resources/tui/themes/tokyo-night.tcss | 12 ++ .../core/commands/tui/ThemeSyntaxPaletteTest.java | 160 +++++++++++++++++++++ 20 files changed, 367 insertions(+), 2 deletions(-) diff --git a/docs/user-manual/modules/ROOT/images/jbang/camel-tui-theme-nord-source.png b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-theme-nord-source.png new file mode 100644 index 000000000000..f4426b858328 Binary files /dev/null and b/docs/user-manual/modules/ROOT/images/jbang/camel-tui-theme-nord-source.png differ diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-settings.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-settings.adoc index 85e17f2d1bc5..52abe9c63f1d 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-settings.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-tui-settings.adoc @@ -96,8 +96,17 @@ warning, error) and borders adapt for readability on each palette. A few themes the brand accent for their own identity: Monochrome, CRT and Turbo Pascal. The Turbo Pascal theme recreates Borland's IDE (yellow text on the blue editor field, cyan window frames, grey menu bar) and also restyles source code the way that editor did: reserved -words in white, code in yellow, comments in grey. Other themes highlight code with a -fixed Monokai (dark) or GitHub-inspired (light) palette. +words in white, code in yellow, comments in grey. + +Source code in the *Source* tab and in code blocks of AI answers is colored to match the +theme. Themes based on an editor color scheme (Dracula, Nord, Solarized, Gruvbox, +Catppuccin, Tokyo Night, Rosé Pine, Kanagawa and Everforest) use that scheme's own colors +for keywords, strings, comments and so on. Where a scheme's color is too faint to read on +the TUI background, it is made slightly lighter (or darker on light themes). Dark, +Monochrome and CRT highlight code with the Monokai palette, and Light with a +GitHub-inspired palette. + +image::jbang/camel-tui-theme-nord-source.png[A YAML route in the Source tab with the Nord theme's code colors] Your choice is remembered: it is saved as `camel.tui.theme` in `.camel-cli.properties` and restored the next time you open the TUI. diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-frappe.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-frappe.tcss index bc1e0bbf36e6..d1e3a7c11ed5 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-frappe.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-frappe.tcss @@ -52,3 +52,14 @@ $frappe-fg: #C6D0F5; #diagram-action { color: #CA9EE6; } #diagram-eip { color: #BABBF1; } #diagram-default { color: #737994; } + +/* + * Syntax tokens (optional), after the Catppuccin style guide: mauve keywords, green strings, blue functions, yellow types, peach constants, overlay2 comments. + */ +#syntax-comment { color: #949CBB; } +#syntax-string { color: #A6D189; } +#syntax-keyword { color: #CA9EE6; } +#syntax-function { color: #8CAAEE; } +#syntax-type { color: #E5C890; } +#syntax-constant { color: #EF9F76; } +#syntax-text { color: $frappe-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-latte.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-latte.tcss index 295d336a3db3..e7cf7549bc23 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-latte.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-latte.tcss @@ -52,3 +52,15 @@ $latte-fg: #4C4F69; #diagram-action { color: #8839EF; } #diagram-eip { color: #7287FD; } #diagram-default { color: #9CA0B0; } + +/* + * Syntax tokens (optional), after the Catppuccin style guide: mauve keywords, green strings, blue functions, yellow types, peach constants, overlay2 comments. + * Lifted to a 3:1 contrast on this background: string, type, constant. + */ +#syntax-comment { color: #7C7F93; } +#syntax-string { color: #3E9A29; } +#syntax-keyword { color: #8839EF; } +#syntax-function { color: #1E66F5; } +#syntax-type { color: #BD7819; } +#syntax-constant { color: #EC5701; } +#syntax-text { color: $latte-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-mocha.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-mocha.tcss index e704f5f4828f..01614f5fe492 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-mocha.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/catppuccin-mocha.tcss @@ -52,3 +52,14 @@ $mocha-fg: #CDD6F4; #diagram-action { color: #CBA6F7; } #diagram-eip { color: #B4BEFE; } #diagram-default { color: #6C7086; } + +/* + * Syntax tokens (optional), after the Catppuccin style guide: mauve keywords, green strings, blue functions, yellow types, peach constants, overlay2 comments. + */ +#syntax-comment { color: #9399B2; } +#syntax-string { color: #A6E3A1; } +#syntax-keyword { color: #CBA6F7; } +#syntax-function { color: #89B4FA; } +#syntax-type { color: #F9E2AF; } +#syntax-constant { color: #FAB387; } +#syntax-text { color: $mocha-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dracula.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dracula.tcss index d7e616fd87c5..c56ad31f1681 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dracula.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/dracula.tcss @@ -52,3 +52,14 @@ $dracula-fg: #F8F8F2; #diagram-action { color: #FF79C6; } #diagram-eip { color: #BD93F9; } #diagram-default { color: #6272A4; } + +/* + * Syntax tokens (optional), after the Dracula spec: pink keywords, yellow strings, green functions, cyan types, purple constants. + */ +#syntax-comment { color: #6272A4; } +#syntax-string { color: #F1FA8C; } +#syntax-keyword { color: #FF79C6; } +#syntax-function { color: #50FA7B; } +#syntax-type { color: #8BE9FD; } +#syntax-constant { color: #BD93F9; } +#syntax-text { color: $dracula-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest-light.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest-light.tcss index 834ea0ba151a..8c6733b83606 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest-light.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest-light.tcss @@ -52,3 +52,15 @@ $everforest-fg: #5C6A72; #diagram-action { color: #E66868; } #diagram-eip { color: #DF69BA; } #diagram-default { color: #939F91; } + +/* + * Syntax tokens (optional), after Everforest: red keywords, aqua strings, green functions, yellow types, purple constants, grey comments. + * Lifted to a 3:1 contrast on this background: comment, string, function, type, constant. + */ +#syntax-comment { color: #82907F; } +#syntax-string { color: #329D75; } +#syntax-keyword { color: #F85552; } +#syntax-function { color: #829401; } +#syntax-type { color: #B68300; } +#syntax-constant { color: #DC5CB4; } +#syntax-text { color: $everforest-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest.tcss index 72fe0546c831..80dc13093bb5 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/everforest.tcss @@ -52,3 +52,14 @@ $everforest-fg: #D3C6AA; #diagram-action { color: #E69875; } #diagram-eip { color: #D699B6; } #diagram-default { color: #7A8478; } + +/* + * Syntax tokens (optional), after Everforest: red keywords, aqua strings, green functions, yellow types, purple constants, grey comments. + */ +#syntax-comment { color: #859289; } +#syntax-string { color: #83C092; } +#syntax-keyword { color: #E67E80; } +#syntax-function { color: #A7C080; } +#syntax-type { color: #DBBC7F; } +#syntax-constant { color: #D699B6; } +#syntax-text { color: $everforest-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-dark.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-dark.tcss index ef54c4a452ca..7167f0875fe4 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-dark.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-dark.tcss @@ -52,3 +52,14 @@ $gruvbox-fg: #EBDBB2; #diagram-action { color: #D3869B; } #diagram-eip { color: #8EC07C; } #diagram-default { color: #A89984; } + +/* + * Syntax tokens (optional), after gruvbox: red keywords, green strings, aqua annotations, yellow types, purple constants. + */ +#syntax-comment { color: #928374; } +#syntax-string { color: #B8BB26; } +#syntax-keyword { color: #FB4934; } +#syntax-function { color: #8EC07C; } +#syntax-type { color: #FABD2F; } +#syntax-constant { color: #D3869B; } +#syntax-text { color: $gruvbox-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-light.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-light.tcss index 8cf77b57ad8e..078820641fc7 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-light.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/gruvbox-light.tcss @@ -52,3 +52,14 @@ $gruvbox-fg: #3C3836; #diagram-action { color: #8F3F71; } #diagram-eip { color: #427B58; } #diagram-default { color: #928374; } + +/* + * Syntax tokens (optional), after gruvbox: red keywords, green strings, aqua annotations, yellow types, purple constants. + */ +#syntax-comment { color: #928374; } +#syntax-string { color: #79740E; } +#syntax-keyword { color: #9D0006; } +#syntax-function { color: #427B58; } +#syntax-type { color: #B57614; } +#syntax-constant { color: #8F3F71; } +#syntax-text { color: $gruvbox-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/kanagawa.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/kanagawa.tcss index fa12fdd01a4d..64b25a32b449 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/kanagawa.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/kanagawa.tcss @@ -52,3 +52,14 @@ $kanagawa-fg: #DCD7BA; #diagram-action { color: #D27E99; } #diagram-eip { color: #957FB8; } #diagram-default { color: #727169; } + +/* + * Syntax tokens (optional), after Kanagawa: oniViolet keywords, springGreen strings, crystalBlue functions, waveAqua types, surimiOrange constants, fujiGray comments. + */ +#syntax-comment { color: #727169; } +#syntax-string { color: #98BB6C; } +#syntax-keyword { color: #957FB8; } +#syntax-function { color: #7E9CD8; } +#syntax-type { color: #7AA89F; } +#syntax-constant { color: #FFA066; } +#syntax-text { color: $kanagawa-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord-light.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord-light.tcss index bb37a863be54..cc41a51a451e 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord-light.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord-light.tcss @@ -52,3 +52,14 @@ $nord-fg: #2E3440; #diagram-action { color: #B48EAD; } #diagram-eip { color: #5E81AC; } #diagram-default { color: #7B88A1; } + +/* + * Syntax tokens (optional), after Nord has no light editor scheme: Nord's hues darkened to read on the light base. + */ +#syntax-comment { color: #7B88A1; } +#syntax-string { color: #5E8045; } +#syntax-keyword { color: #5E81AC; } +#syntax-function { color: #3B7A8A; } +#syntax-type { color: #4C7A79; } +#syntax-constant { color: #8F5C88; } +#syntax-text { color: $nord-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord.tcss index b32e287beeee..b811774ef40f 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/nord.tcss @@ -52,3 +52,15 @@ $nord-fg: #D8DEE9; #diagram-action { color: #B48EAD; } #diagram-eip { color: #5E81AC; } #diagram-default { color: #4C566A; } + +/* + * Syntax tokens (optional), after Nord's own editor mapping (frost keywords and functions, green strings, purple numbers). + * Lifted to a 3:1 contrast on this background: comment. + */ +#syntax-comment { color: #72809B; } +#syntax-string { color: #A3BE8C; } +#syntax-keyword { color: #81A1C1; } +#syntax-function { color: #88C0D0; } +#syntax-type { color: #8FBCBB; } +#syntax-constant { color: #B48EAD; } +#syntax-text { color: $nord-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine-moon.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine-moon.tcss index 251fad5896f8..a7ea736e89bb 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine-moon.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine-moon.tcss @@ -52,3 +52,14 @@ $moon-fg: #E0DEF4; #diagram-action { color: #EB6F92; } #diagram-eip { color: #C4A7E7; } #diagram-default { color: #6E6A86; } + +/* + * Syntax tokens (optional), after Rosé Pine: pine keywords, gold strings, rose functions, foam types, iris constants, muted comments. + */ +#syntax-comment { color: #6E6A86; } +#syntax-string { color: #F6C177; } +#syntax-keyword { color: #3E8FB0; } +#syntax-function { color: #EA9A97; } +#syntax-type { color: #9CCFD8; } +#syntax-constant { color: #C4A7E7; } +#syntax-text { color: $moon-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine.tcss index 1a4aab49fb32..163bd8e19820 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/rose-pine.tcss @@ -52,3 +52,14 @@ $rosepine-fg: #E0DEF4; #diagram-action { color: #EB6F92; } #diagram-eip { color: #C4A7E7; } #diagram-default { color: #6E6A86; } + +/* + * Syntax tokens (optional), after Rosé Pine: pine keywords, gold strings, rose functions, foam types, iris constants, muted comments. + */ +#syntax-comment { color: #6E6A86; } +#syntax-string { color: #F6C177; } +#syntax-keyword { color: #31748F; } +#syntax-function { color: #EBBCBA; } +#syntax-type { color: #9CCFD8; } +#syntax-constant { color: #C4A7E7; } +#syntax-text { color: $rosepine-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-dark.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-dark.tcss index 02c270178775..28a56d4530f9 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-dark.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-dark.tcss @@ -52,3 +52,15 @@ $solar-fg: #839496; #diagram-action { color: #D33682; } #diagram-eip { color: #6C71C4; } #diagram-default { color: #586E75; } + +/* + * Syntax tokens (optional), after Solarized: green keywords, cyan strings, blue functions, yellow types, magenta numbers. + * Lifted to a 3:1 contrast on this background: comment. + */ +#syntax-comment { color: #5F767E; } +#syntax-string { color: #2AA198; } +#syntax-keyword { color: #859900; } +#syntax-function { color: #268BD2; } +#syntax-type { color: #B58900; } +#syntax-constant { color: #D33682; } +#syntax-text { color: $solar-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-light.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-light.tcss index b607d7083ad6..3ab3d8ffe293 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-light.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/solarized-light.tcss @@ -52,3 +52,15 @@ $solar-fg: #657B83; #diagram-action { color: #D33682; } #diagram-eip { color: #6C71C4; } #diagram-default { color: #93A1A1; } + +/* + * Syntax tokens (optional), after Solarized: green keywords, cyan strings, blue functions, yellow types, magenta numbers. + * Lifted to a 3:1 contrast on this background: comment, string, keyword, type. + */ +#syntax-comment { color: #7F8F8F; } +#syntax-string { color: #289B92; } +#syntax-keyword { color: #819400; } +#syntax-function { color: #268BD2; } +#syntax-type { color: #B08500; } +#syntax-constant { color: #D33682; } +#syntax-text { color: $solar-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/theme.md b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/theme.md index 1afba159483e..e2fe32c9e2b4 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/theme.md +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/theme.md @@ -83,6 +83,20 @@ built-in Monokai palette (dark themes) or GitHub-inspired palette (light themes). Define them when the theme calls for its own editor look, as the Turbo Pascal theme does, but keep code readable. +Themes ported from an editor color scheme (Dracula, Nord, Solarized, Gruvbox, +Catppuccin, Tokyo Night, Rosé Pine, Kanagawa, Everforest) take the scheme's own +code colors. Dark, Light, Monochrome and CRT keep the built-in palette. + +A theme defines all 7 tokens or none. `ThemeSyntaxPaletteTest` checks every +theme, including the built-in fallback, for: + +- a contrast of at least 3:1 between each code color and `base-bg`; lift a + scheme color that falls short (lighter on dark themes, darker on light ones) + and keep its hue, noting it in the stylesheet comment +- keywords, strings and comments in different colors, and none of them in the + color of plain code text +- `syntax-text` equal to `base-fg` (except Turbo Pascal and the fallback themes) + | Token | Type | Purpose | |-------|------|---------| | `syntax-comment` | fg | Comments | diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/tokyo-night.tcss b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/tokyo-night.tcss index d84a93243c5b..c6a437a37b39 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/tokyo-night.tcss +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/themes/tokyo-night.tcss @@ -52,3 +52,15 @@ $tokyo-fg: #A9B1D6; #diagram-action { color: #BB9AF7; } #diagram-eip { color: #73DACA; } #diagram-default { color: #565F89; } + +/* + * Syntax tokens (optional), after Tokyo Night: purple keywords, green strings, blue functions, cyan types, orange constants. + * Lifted to a 3:1 contrast on this background: comment. + */ +#syntax-comment { color: #5D6794; } +#syntax-string { color: #9ECE6A; } +#syntax-keyword { color: #BB9AF7; } +#syntax-function { color: #7AA2F7; } +#syntax-type { color: #2AC3DE; } +#syntax-constant { color: #FF9E64; } +#syntax-text { color: $tokyo-fg; } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeSyntaxPaletteTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeSyntaxPaletteTest.java new file mode 100644 index 000000000000..97b59f303c15 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ThemeSyntaxPaletteTest.java @@ -0,0 +1,160 @@ +/* + * 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 java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import dev.tamboui.style.Color; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Guards the code colors of every theme: whether they come from the theme's own {@code syntax-*} tokens or from the + * Monokai / GitHub fallback, code must stay readable on the theme's background. + */ +@Isolated +class ThemeSyntaxPaletteTest { + + /** Minimum contrast of a code color on the theme background (WCAG ratio; 3:1 is the large-text level). */ + private static final double MIN_CONTRAST = 3.0; + + private static final List<String> SYNTAX_TOKENS = List.of( + "syntax-comment", "syntax-string", "syntax-keyword", "syntax-function", "syntax-type", + "syntax-constant", "syntax-text"); + + /** Themes that keep the built-in Monokai (dark) or GitHub (light) code palette on purpose. */ + private static final Set<ThemeMode> FALLBACK_PALETTE = EnumSet.of( + ThemeMode.DARK, ThemeMode.LIGHT, ThemeMode.MONOCHROME, ThemeMode.CRT); + + @BeforeEach + void setUp() { + Theme.resetForTesting(); + } + + @AfterEach + void tearDown() { + Theme.resetForTesting(); + } + + @ParameterizedTest + @EnumSource(ThemeMode.class) + void codeColorsAreReadableOnTheBackground(ThemeMode mode) { + Theme.setMode(mode.id()); + Color bg = Theme.baseBg(); + syntaxColors().forEach((name, color) -> { + double ratio = contrast(color, bg); + assertTrue(ratio >= MIN_CONTRAST, + () -> String.format("%s: %s %s has contrast %.2f on %s, below %.1f", + mode.id(), name, color, ratio, bg, MIN_CONTRAST)); + }); + } + + @ParameterizedTest + @EnumSource(ThemeMode.class) + void keywordsStringsAndCommentsStandApart(ThemeMode mode) { + Theme.setMode(mode.id()); + Color keyword = Theme.syntaxKeyword(); + Color string = Theme.syntaxString(); + Color comment = Theme.syntaxComment(); + Color text = Theme.syntaxText(); + assertNotEquals(keyword, string, mode.id() + ": keywords and strings share a color"); + assertNotEquals(keyword, comment, mode.id() + ": keywords and comments share a color"); + assertNotEquals(string, comment, mode.id() + ": strings and comments share a color"); + for (Color c : List.of(keyword, string, comment)) { + assertNotEquals(text, c, mode.id() + ": plain code text has the color of keywords, strings or comments"); + } + } + + @ParameterizedTest + @EnumSource(ThemeMode.class) + void editorSchemeThemesDefineTheirOwnCodePalette(ThemeMode mode) throws IOException { + String css = stylesheet(mode); + if (FALLBACK_PALETTE.contains(mode)) { + assertTrue(SYNTAX_TOKENS.stream().noneMatch(t -> css.contains("#" + t)), + mode.id() + " is expected to keep the built-in code palette"); + return; + } + // A theme defines all syntax tokens or none, so a half-defined palette never mixes with Monokai. + for (String token : SYNTAX_TOKENS) { + assertTrue(css.contains("#" + token), mode.id() + " lacks " + token); + } + Theme.setMode(mode.id()); + Color fallback = mode.isLight() ? SyntaxHighlighter.LIGHT_KEYWORD : SyntaxHighlighter.MONOKAI_KEYWORD; + assertNotEquals(fallback, Theme.syntaxKeyword(), mode.id() + " still shows the fallback keyword color"); + } + + @ParameterizedTest + @EnumSource(ThemeMode.class) + void plainCodeTextMatchesTheThemeText(ThemeMode mode) { + if (FALLBACK_PALETTE.contains(mode) || mode == ThemeMode.TURBO_PASCAL) { + return; + } + Theme.setMode(mode.id()); + assertEquals(Theme.baseFg(), Theme.syntaxText(), mode.id() + ": code text should be the theme's text color"); + } + + private static Map<String, Color> syntaxColors() { + Map<String, Color> colors = new LinkedHashMap<>(); + colors.put("comment", Theme.syntaxComment()); + colors.put("string", Theme.syntaxString()); + colors.put("keyword", Theme.syntaxKeyword()); + colors.put("function", Theme.syntaxFunction()); + colors.put("type", Theme.syntaxType()); + colors.put("constant", Theme.syntaxConstant()); + colors.put("text", Theme.syntaxText()); + return colors; + } + + private static String stylesheet(ThemeMode mode) throws IOException { + try (InputStream in = ThemeSyntaxPaletteTest.class.getClassLoader() + .getResourceAsStream(mode.stylesheetResource())) { + assertNotNull(in, "missing " + mode.stylesheetResource()); + return new String(in.readAllBytes(), StandardCharsets.UTF_8); + } + } + + static double contrast(Color a, Color b) { + double la = luminance(a); + double lb = luminance(b); + return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05); + } + + private static double luminance(Color color) { + Color.Rgb rgb = color.toRgb(); + return 0.2126 * channel(rgb.r()) + 0.7152 * channel(rgb.g()) + 0.0722 * channel(rgb.b()); + } + + private static double channel(int value) { + double c = value / 255.0; + return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); + } +}
