This is an automated email from the ASF dual-hosted git repository.
apkhmv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new c6a3e7df0ee IGNITE-27726 CLI: Add color theme configuration for
improved readability (#7540)
c6a3e7df0ee is described below
commit c6a3e7df0ee0adbfb3b7dc9bc26c39f6ffc3f258
Author: Aleksandr Pakhomov <[email protected]>
AuthorDate: Mon Feb 9 15:06:03 2026 +0300
IGNITE-27726 CLI: Add color theme configuration for improved readability
(#7540)
---
.../java/org/apache/ignite/internal/cli/Main.java | 16 ++
.../SqlAttributedStringHighlighter.java | 53 +++--
.../ignite/internal/cli/config/CliConfigKeys.java | 7 +-
.../internal/cli/core/style/AnsiStringSupport.java | 97 +++++++--
.../internal/cli/core/style/ColorScheme.java | 229 +++++++++++++++++++++
.../cli/core/style/ColorSchemeProvider.java | 30 +++
.../cli/core/style/DefaultColorSchemeProvider.java | 43 ++++
.../cli/core/style/AnsiStringSupportTest.java | 95 +++++++++
.../internal/cli/core/style/ColorSchemeTest.java | 98 +++++++++
9 files changed, 633 insertions(+), 35 deletions(-)
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/Main.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/Main.java
index 5625b842840..2461a699b17 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/Main.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/Main.java
@@ -31,11 +31,14 @@ import java.util.logging.LogManager;
import java.util.stream.Collectors;
import org.apache.ignite.internal.cli.commands.TopLevelCliCommand;
import org.apache.ignite.internal.cli.config.ConfigDefaultValueProvider;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
import org.apache.ignite.internal.cli.config.StateFolderProvider;
import
org.apache.ignite.internal.cli.core.exception.handler.PicocliExecutionExceptionHandler;
import
org.apache.ignite.internal.cli.core.flow.question.JlineQuestionWriterReaderFactory;
import org.apache.ignite.internal.cli.core.flow.question.QuestionAskerFactory;
import
org.apache.ignite.internal.cli.core.repl.executor.ReplExecutorProviderImpl;
+import org.apache.ignite.internal.cli.core.style.AnsiStringSupport;
+import org.apache.ignite.internal.cli.core.style.ColorScheme;
import org.fusesource.jansi.AnsiConsole;
import org.jline.terminal.Terminal;
import picocli.CommandLine;
@@ -60,6 +63,7 @@ public class Main {
int exitCode = 0;
ApplicationContextBuilder builder =
ApplicationContext.builder(Environment.CLI).deduceEnvironment(false);
try (MicronautFactory micronautFactory = new
MicronautFactory(builder.start())) {
+ initColorScheme(micronautFactory);
if (interactiveMode) {
// REPL mode: full initialization with Jansi ANSI console and
JLine terminal.
AnsiConsole.systemInstall();
@@ -94,6 +98,18 @@ public class Main {
return System.console() != null;
}
+ /** Initializes the color scheme provider to read from configuration
dynamically. */
+ private static void initColorScheme(MicronautFactory micronautFactory)
throws Exception {
+ ConfigManagerProvider configProvider =
micronautFactory.create(ConfigManagerProvider.class);
+ // Set a provider that reads from config each time, so changes take
effect immediately
+ AnsiStringSupport.setColorSchemeProvider(() -> {
+ String schemeName =
configProvider.get().getCurrentProperty("ignite.cli.color-scheme");
+ ColorScheme scheme = ColorScheme.fromString(schemeName);
+ ColorScheme result = scheme != null ? scheme :
ColorScheme.SOLARIZED_DARK;
+ return result;
+ });
+ }
+
/** Needed for immediate REPL mode and for running a command which will
stay in REPL mode so we need to init it once. */
private static void initReplExecutor(MicronautFactory micronautFactory)
throws Exception {
ReplExecutorProviderImpl replExecutorProvider =
micronautFactory.create(ReplExecutorProviderImpl.class);
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/SqlAttributedStringHighlighter.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/SqlAttributedStringHighlighter.java
index 11a956ba0f5..4735e9e94c8 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/SqlAttributedStringHighlighter.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/treesitter/highlighter/SqlAttributedStringHighlighter.java
@@ -17,10 +17,11 @@
package org.apache.ignite.internal.cli.commands.treesitter.highlighter;
-import java.util.Map;
import org.apache.ignite.internal.cli.commands.treesitter.parser.Indexer;
import org.apache.ignite.internal.cli.commands.treesitter.parser.Parser;
import org.apache.ignite.internal.cli.commands.treesitter.parser.SqlTokenType;
+import org.apache.ignite.internal.cli.core.style.AnsiStringSupport;
+import org.apache.ignite.internal.cli.core.style.ColorScheme;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
@@ -29,26 +30,24 @@ import org.jline.utils.AttributedStyle;
* Highlighter for SQL text. The highlighted text is returned as an
AttributedString.
*/
public class SqlAttributedStringHighlighter {
- private static final Map<SqlTokenType, Integer> colorMap = Map.of(
- SqlTokenType.KEYWORD, 215,
- SqlTokenType.IDENTIFIER, 254,
- SqlTokenType.BRACKET, 248,
- SqlTokenType.LITERAL, 106,
- SqlTokenType.SPACE, 0,
- SqlTokenType.COMMA, 248,
- SqlTokenType.EQUAL, 248,
- SqlTokenType.STAR, 248,
- SqlTokenType.SEMICOLON, 248,
- SqlTokenType.UNKNOWN, 248
- );
-
/**
- * Highlights the input SQL text with ANSI colors.
+ * Highlights the input SQL text with ANSI colors using the current color
scheme.
*
* @param text The input SQL text.
* @return The highlighted SQL text.
*/
public static AttributedString highlight(String text) {
+ return highlight(text, AnsiStringSupport.getColorScheme());
+ }
+
+ /**
+ * Highlights the input SQL text with ANSI colors using the specified
color scheme.
+ *
+ * @param text The input SQL text.
+ * @param scheme The color scheme to use.
+ * @return The highlighted SQL text.
+ */
+ public static AttributedString highlight(String text, ColorScheme scheme) {
var as = new AttributedStringBuilder();
var tree = Parser.parseSql(text);
@@ -56,11 +55,33 @@ public class SqlAttributedStringHighlighter {
for (int i = 0; i < text.length(); i++) {
SqlTokenType token = tokens[i];
- int color = colorMap.getOrDefault(token, 1);
+ int color = getColorForToken(token, scheme);
var style = AttributedStyle.DEFAULT.foreground(color);
as.style(style).append(text.charAt(i));
}
return as.toAttributedString();
}
+
+ private static int getColorForToken(SqlTokenType token, ColorScheme
scheme) {
+ switch (token) {
+ case KEYWORD:
+ return scheme.keywordColor();
+ case IDENTIFIER:
+ return scheme.identifierColor();
+ case LITERAL:
+ return scheme.stringColor();
+ case BRACKET:
+ case COMMA:
+ case EQUAL:
+ case STAR:
+ case SEMICOLON:
+ return scheme.punctuationColor();
+ case SPACE:
+ return 0;
+ case UNKNOWN:
+ default:
+ return scheme.punctuationColor();
+ }
+ }
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/config/CliConfigKeys.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/config/CliConfigKeys.java
index c06178c5777..eb833611910 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/config/CliConfigKeys.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/config/CliConfigKeys.java
@@ -89,7 +89,10 @@ public enum CliConfigKeys {
OUTPUT_TRUNCATE(Constants.OUTPUT_TRUNCATE),
/** Maximum column width property name. */
- OUTPUT_MAX_COLUMN_WIDTH(Constants.OUTPUT_MAX_COLUMN_WIDTH);
+ OUTPUT_MAX_COLUMN_WIDTH(Constants.OUTPUT_MAX_COLUMN_WIDTH),
+
+ /** Color scheme property name (dark, light). */
+ COLOR_SCHEME(Constants.COLOR_SCHEME);
private final String value;
@@ -167,6 +170,8 @@ public enum CliConfigKeys {
public static final String OUTPUT_TRUNCATE =
"ignite.cli.output.truncate";
public static final String OUTPUT_MAX_COLUMN_WIDTH =
"ignite.cli.output.max-column-width";
+
+ public static final String COLOR_SCHEME = "ignite.cli.color-scheme";
}
CliConfigKeys(String value) {
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/AnsiStringSupport.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/AnsiStringSupport.java
index 5654659ab55..4f6d8defd52 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/AnsiStringSupport.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/AnsiStringSupport.java
@@ -17,20 +17,46 @@
package org.apache.ignite.internal.cli.core.style;
+import java.util.concurrent.atomic.AtomicReference;
import picocli.CommandLine.Help.Ansi;
/**
* Utility class with ANSI string support.
*/
public final class AnsiStringSupport {
+ private static final AtomicReference<ColorSchemeProvider> SCHEME_PROVIDER =
+ new AtomicReference<>(() -> ColorScheme.SOLARIZED_DARK);
+
private AnsiStringSupport() {}
+ /**
+ * Sets the color scheme provider for dynamic color scheme resolution.
+ *
+ * @param provider Color scheme provider.
+ */
+ public static void setColorSchemeProvider(ColorSchemeProvider provider) {
+ SCHEME_PROVIDER.set(provider != null ? provider : () ->
ColorScheme.SOLARIZED_DARK);
+ }
+
+ /**
+ * Returns the current color scheme from the provider.
+ *
+ * @return Current color scheme.
+ */
+ public static ColorScheme getColorScheme() {
+ return SCHEME_PROVIDER.get().colorScheme();
+ }
+
public static String ansi(String markupText) {
return Ansi.AUTO.string(markupText);
}
public static Fg fg(Color color) {
- return new Fg(color);
+ return new Fg(color, getColorScheme());
+ }
+
+ public static Fg fg(Color color, ColorScheme scheme) {
+ return new Fg(color, scheme);
}
/** Can mark the string as a ANSI string. */
@@ -43,11 +69,13 @@ public final class AnsiStringSupport {
*/
public static class Fg implements Marker {
private final Color color;
+ private final ColorScheme scheme;
private Style style;
- private Fg(Color color) {
+ private Fg(Color color, ColorScheme scheme) {
this.color = color;
+ this.scheme = scheme;
}
public Fg with(Style style) {
@@ -58,10 +86,11 @@ public final class AnsiStringSupport {
/** Marks given text with the configured before style. */
@Override
public String mark(String textToMark) {
+ int colorCode = color.getCode(scheme);
if (style == Style.BOLD) {
- return String.format("@|fg(%d),bold %s|@", color.code,
textToMark);
+ return String.format("@|fg(%d),bold %s|@", colorCode,
textToMark);
}
- return String.format("@|fg(%d) %s|@", color.code, textToMark);
+ return String.format("@|fg(%d) %s|@", colorCode, textToMark);
}
}
@@ -82,22 +111,54 @@ public final class AnsiStringSupport {
}
/**
- * Represents ansi colors that are used in CLI.
+ * Represents semantic colors that are used in CLI.
+ * Actual ANSI color codes are resolved based on the current color scheme.
*/
public enum Color {
- RED(1),
- GREEN(2),
- YELLOW(3),
- BLUE(31),
- YELLOW_DARK(215),
- GREEN_DARK(22),
- GRAY(246),
- WHITE(252);
-
- Color(int code) {
- this.code = code;
+ /** Error color (red variants). */
+ RED,
+ /** Success color (green variants). */
+ GREEN,
+ /** Warning/option color (yellow variants). */
+ YELLOW,
+ /** Info color (blue variants). */
+ BLUE,
+ /** Keyword color for syntax highlighting. */
+ YELLOW_DARK,
+ /** String literal color for syntax highlighting. */
+ GREEN_DARK,
+ /** Secondary/muted text color. */
+ GRAY,
+ /** Primary text color. */
+ WHITE;
+
+ /**
+ * Returns the ANSI color code for this semantic color in the given
scheme.
+ *
+ * @param scheme Color scheme to use.
+ * @return ANSI color code.
+ */
+ public int getCode(ColorScheme scheme) {
+ switch (this) {
+ case RED:
+ return scheme.errorColor();
+ case GREEN:
+ return scheme.successColor();
+ case YELLOW:
+ return scheme.warningColor();
+ case BLUE:
+ return scheme.infoColor();
+ case YELLOW_DARK:
+ return scheme.keywordColor();
+ case GREEN_DARK:
+ return scheme.stringColor();
+ case GRAY:
+ return scheme.mutedColor();
+ case WHITE:
+ return scheme.primaryColor();
+ default:
+ return scheme.primaryColor();
+ }
}
-
- private final int code;
}
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/ColorScheme.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/ColorScheme.java
new file mode 100644
index 00000000000..9c53cdd3a9b
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/ColorScheme.java
@@ -0,0 +1,229 @@
+/*
+ * 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.ignite.internal.cli.core.style;
+
+import java.util.Locale;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Defines color schemes for CLI output.
+ * Each scheme provides ANSI 256-color codes optimized for different terminal
backgrounds.
+ */
+public enum ColorScheme {
+ /** Default color scheme for dark terminal backgrounds. */
+ DARK(
+ // UI colors
+ 1, // error: bright red
+ 2, // success: bright green
+ 3, // warning: bright yellow
+ 33, // info: bright blue
+ 246, // muted: gray
+ 252, // primary: white
+ // Syntax highlighting colors
+ 214, // keyword: orange
+ 2, // string: bright green
+ 33, // number: bright blue
+ 252, // bracket: white
+ 246, // punctuation: gray
+ 254 // identifier: bright white
+ ),
+
+ /** Default color scheme for light terminal backgrounds. */
+ LIGHT(
+ // UI colors
+ 124, // error: dark red
+ 22, // success: dark green
+ 130, // warning: dark orange/brown
+ 18, // info: dark blue
+ 242, // muted: dark gray
+ 0, // primary: black
+ // Syntax highlighting colors
+ 130, // keyword: dark orange/brown
+ 22, // string: dark green
+ 18, // number: dark blue
+ 0, // bracket: black
+ 242, // punctuation: dark gray
+ 0 // identifier: black
+ ),
+
+ /**
+ * Solarized Dark theme by Ethan Schoonover.
+ * Designed for dark backgrounds with carefully chosen colors for
readability.
+ * See: https://ethanschoonover.com/solarized/
+ */
+ SOLARIZED_DARK(
+ // UI colors (Solarized accent colors)
+ 160, // error: red (#dc322f)
+ 106, // success: green (#859900)
+ 136, // warning: yellow (#b58900)
+ 33, // info: blue (#268bd2)
+ 246, // muted: base0 (#839496)
+ 252, // primary: bright white (for better visibility)
+ // Syntax highlighting colors
+ 166, // keyword: orange (#cb4b16)
+ 37, // string: cyan (#2aa198)
+ 33, // number: blue (#268bd2)
+ 252, // bracket: bright white
+ 246, // punctuation: base0 (#839496)
+ 252 // identifier: bright white
+ ),
+
+ /**
+ * Solarized Light theme by Ethan Schoonover.
+ * Designed for light backgrounds with carefully chosen colors for
readability.
+ * See: https://ethanschoonover.com/solarized/
+ */
+ SOLARIZED_LIGHT(
+ // UI colors (Solarized accent colors)
+ 160, // error: red (#dc322f)
+ 106, // success: green (#859900)
+ 136, // warning: yellow (#b58900)
+ 33, // info: blue (#268bd2)
+ 247, // muted: base1 (#93a1a1)
+ 66, // primary: base00 (#657b83)
+ // Syntax highlighting colors
+ 166, // keyword: orange (#cb4b16)
+ 37, // string: cyan (#2aa198)
+ 33, // number: blue (#268bd2)
+ 66, // bracket: base00 (#657b83)
+ 247, // punctuation: base1 (#93a1a1)
+ 66 // identifier: base00 (#657b83)
+ );
+
+ private final int errorColor;
+ private final int successColor;
+ private final int warningColor;
+ private final int infoColor;
+ private final int mutedColor;
+ private final int primaryColor;
+ private final int keywordColor;
+ private final int stringColor;
+ private final int numberColor;
+ private final int bracketColor;
+ private final int punctuationColor;
+ private final int identifierColor;
+
+ ColorScheme(
+ int errorColor,
+ int successColor,
+ int warningColor,
+ int infoColor,
+ int mutedColor,
+ int primaryColor,
+ int keywordColor,
+ int stringColor,
+ int numberColor,
+ int bracketColor,
+ int punctuationColor,
+ int identifierColor
+ ) {
+ this.errorColor = errorColor;
+ this.successColor = successColor;
+ this.warningColor = warningColor;
+ this.infoColor = infoColor;
+ this.mutedColor = mutedColor;
+ this.primaryColor = primaryColor;
+ this.keywordColor = keywordColor;
+ this.stringColor = stringColor;
+ this.numberColor = numberColor;
+ this.bracketColor = bracketColor;
+ this.punctuationColor = punctuationColor;
+ this.identifierColor = identifierColor;
+ }
+
+ /** Returns ANSI color code for error messages. */
+ public int errorColor() {
+ return errorColor;
+ }
+
+ /** Returns ANSI color code for success messages. */
+ public int successColor() {
+ return successColor;
+ }
+
+ /** Returns ANSI color code for warning/option messages. */
+ public int warningColor() {
+ return warningColor;
+ }
+
+ /** Returns ANSI color code for info messages. */
+ public int infoColor() {
+ return infoColor;
+ }
+
+ /** Returns ANSI color code for muted/secondary text. */
+ public int mutedColor() {
+ return mutedColor;
+ }
+
+ /** Returns ANSI color code for primary text. */
+ public int primaryColor() {
+ return primaryColor;
+ }
+
+ /** Returns ANSI color code for syntax keywords. */
+ public int keywordColor() {
+ return keywordColor;
+ }
+
+ /** Returns ANSI color code for string literals. */
+ public int stringColor() {
+ return stringColor;
+ }
+
+ /** Returns ANSI color code for number literals. */
+ public int numberColor() {
+ return numberColor;
+ }
+
+ /** Returns ANSI color code for brackets. */
+ public int bracketColor() {
+ return bracketColor;
+ }
+
+ /** Returns ANSI color code for punctuation (comma, colon, etc.). */
+ public int punctuationColor() {
+ return punctuationColor;
+ }
+
+ /** Returns ANSI color code for identifiers. */
+ public int identifierColor() {
+ return identifierColor;
+ }
+
+ /**
+ * Parses a color scheme from string value.
+ * Supports both underscore and hyphen separators (e.g., "solarized-dark"
or "solarized_dark").
+ *
+ * @param value Color scheme name (case-insensitive).
+ * @return Parsed color scheme or {@code null} if value is invalid.
+ */
+ @Nullable
+ public static ColorScheme fromString(@Nullable String value) {
+ if (value == null || value.isEmpty()) {
+ return null;
+ }
+ try {
+ // Convert hyphens to underscores to support both "solarized-dark"
and "solarized_dark"
+ String normalized = value.toUpperCase(Locale.ROOT).replace('-',
'_');
+ return valueOf(normalized);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/ColorSchemeProvider.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/ColorSchemeProvider.java
new file mode 100644
index 00000000000..cbc95586a26
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/ColorSchemeProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.ignite.internal.cli.core.style;
+
+/**
+ * Provider for the current color scheme.
+ */
+public interface ColorSchemeProvider {
+ /**
+ * Returns the current color scheme.
+ *
+ * @return Current color scheme, never null.
+ */
+ ColorScheme colorScheme();
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/DefaultColorSchemeProvider.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/DefaultColorSchemeProvider.java
new file mode 100644
index 00000000000..9f53bc52304
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/style/DefaultColorSchemeProvider.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ignite.internal.cli.core.style;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+
+/**
+ * Default implementation of {@link ColorSchemeProvider} that reads from CLI
configuration.
+ */
+@Singleton
+public class DefaultColorSchemeProvider implements ColorSchemeProvider {
+ private final ConfigManagerProvider configManagerProvider;
+
+ @Inject
+ public DefaultColorSchemeProvider(ConfigManagerProvider
configManagerProvider) {
+ this.configManagerProvider = configManagerProvider;
+ }
+
+ @Override
+ public ColorScheme colorScheme() {
+ String schemeName =
configManagerProvider.get().getCurrentProperty(CliConfigKeys.COLOR_SCHEME.value());
+ ColorScheme scheme = ColorScheme.fromString(schemeName);
+ return scheme != null ? scheme : ColorScheme.DARK;
+ }
+}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/style/AnsiStringSupportTest.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/style/AnsiStringSupportTest.java
new file mode 100644
index 00000000000..6d00686e98b
--- /dev/null
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/style/AnsiStringSupportTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.ignite.internal.cli.core.style;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.ignite.internal.cli.core.style.AnsiStringSupport.Color;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+class AnsiStringSupportTest {
+
+ @AfterEach
+ void tearDown() {
+ // Reset to default provider
+ AnsiStringSupport.setColorSchemeProvider(() ->
ColorScheme.SOLARIZED_DARK);
+ }
+
+ @Test
+ void defaultSchemeIsSolarizedDark() {
+ AnsiStringSupport.setColorSchemeProvider(null);
+ assertEquals(ColorScheme.SOLARIZED_DARK,
AnsiStringSupport.getColorScheme());
+ }
+
+ @Test
+ void providerIsCalledDynamically() {
+ // Use a mutable holder to change the scheme dynamically
+ final ColorScheme[] holder = {ColorScheme.DARK};
+ AnsiStringSupport.setColorSchemeProvider(() -> holder[0]);
+
+ assertEquals(ColorScheme.DARK, AnsiStringSupport.getColorScheme());
+
+ // Change the scheme
+ holder[0] = ColorScheme.LIGHT;
+ assertEquals(ColorScheme.LIGHT, AnsiStringSupport.getColorScheme());
+ }
+
+ @Test
+ void fgUsesCurrentSchemeColors() {
+ AnsiStringSupport.setColorSchemeProvider(() -> ColorScheme.DARK);
+ String darkResult = AnsiStringSupport.fg(Color.RED).mark("error");
+
+ AnsiStringSupport.setColorSchemeProvider(() -> ColorScheme.LIGHT);
+ String lightResult = AnsiStringSupport.fg(Color.RED).mark("error");
+
+ // The color codes should be different
+ assertTrue(darkResult.contains("fg(" + ColorScheme.DARK.errorColor() +
")"));
+ assertTrue(lightResult.contains("fg(" + ColorScheme.LIGHT.errorColor()
+ ")"));
+ }
+
+ @Test
+ void fgWithExplicitSchemeOverridesGlobal() {
+ AnsiStringSupport.setColorSchemeProvider(() -> ColorScheme.DARK);
+
+ // Even with DARK as global, we can explicitly use LIGHT
+ String lightResult = AnsiStringSupport.fg(Color.RED,
ColorScheme.LIGHT).mark("error");
+ assertTrue(lightResult.contains("fg(" + ColorScheme.LIGHT.errorColor()
+ ")"));
+ }
+
+ @Test
+ void fgWithBoldStyle() {
+ AnsiStringSupport.setColorSchemeProvider(() -> ColorScheme.DARK);
+ String result =
AnsiStringSupport.fg(Color.GREEN).with(AnsiStringSupport.Style.BOLD).mark("success");
+ assertTrue(result.contains("bold"));
+ assertTrue(result.contains("fg(" + ColorScheme.DARK.successColor() +
")"));
+ }
+
+ @Test
+ void colorEnumMapsToCorrectSchemeColors() {
+ assertEquals(ColorScheme.DARK.errorColor(),
Color.RED.getCode(ColorScheme.DARK));
+ assertEquals(ColorScheme.DARK.successColor(),
Color.GREEN.getCode(ColorScheme.DARK));
+ assertEquals(ColorScheme.DARK.warningColor(),
Color.YELLOW.getCode(ColorScheme.DARK));
+ assertEquals(ColorScheme.DARK.infoColor(),
Color.BLUE.getCode(ColorScheme.DARK));
+ assertEquals(ColorScheme.DARK.keywordColor(),
Color.YELLOW_DARK.getCode(ColorScheme.DARK));
+ assertEquals(ColorScheme.DARK.stringColor(),
Color.GREEN_DARK.getCode(ColorScheme.DARK));
+ assertEquals(ColorScheme.DARK.mutedColor(),
Color.GRAY.getCode(ColorScheme.DARK));
+ assertEquals(ColorScheme.DARK.primaryColor(),
Color.WHITE.getCode(ColorScheme.DARK));
+ }
+}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/style/ColorSchemeTest.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/style/ColorSchemeTest.java
new file mode 100644
index 00000000000..3fe874540be
--- /dev/null
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/style/ColorSchemeTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.ignite.internal.cli.core.style;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+class ColorSchemeTest {
+
+ @Test
+ void darkAndLightSchemesHaveDifferentColors() {
+ assertNotEquals(ColorScheme.DARK.errorColor(),
ColorScheme.LIGHT.errorColor());
+ assertNotEquals(ColorScheme.DARK.successColor(),
ColorScheme.LIGHT.successColor());
+ assertNotEquals(ColorScheme.DARK.primaryColor(),
ColorScheme.LIGHT.primaryColor());
+ }
+
+ @Test
+ void solarizedSchemesExist() {
+ assertEquals(160, ColorScheme.SOLARIZED_DARK.errorColor());
+ assertEquals(106, ColorScheme.SOLARIZED_DARK.successColor());
+ assertEquals(160, ColorScheme.SOLARIZED_LIGHT.errorColor());
+ assertEquals(106, ColorScheme.SOLARIZED_LIGHT.successColor());
+ }
+
+ @Test
+ void solarizedDarkAndLightHaveDifferentBaseColors() {
+ // Primary text differs between solarized dark and light
+ assertNotEquals(ColorScheme.SOLARIZED_DARK.primaryColor(),
ColorScheme.SOLARIZED_LIGHT.primaryColor());
+ // Muted text differs
+ assertNotEquals(ColorScheme.SOLARIZED_DARK.mutedColor(),
ColorScheme.SOLARIZED_LIGHT.mutedColor());
+ }
+
+ @Test
+ void fromStringParsesValidSchemes() {
+ assertEquals(ColorScheme.DARK, ColorScheme.fromString("dark"));
+ assertEquals(ColorScheme.DARK, ColorScheme.fromString("DARK"));
+ assertEquals(ColorScheme.LIGHT, ColorScheme.fromString("light"));
+ assertEquals(ColorScheme.LIGHT, ColorScheme.fromString("LIGHT"));
+ }
+
+ @Test
+ void fromStringParsesSolarizedWithHyphen() {
+ assertEquals(ColorScheme.SOLARIZED_DARK,
ColorScheme.fromString("solarized-dark"));
+ assertEquals(ColorScheme.SOLARIZED_DARK,
ColorScheme.fromString("SOLARIZED-DARK"));
+ assertEquals(ColorScheme.SOLARIZED_LIGHT,
ColorScheme.fromString("solarized-light"));
+ assertEquals(ColorScheme.SOLARIZED_LIGHT,
ColorScheme.fromString("SOLARIZED-LIGHT"));
+ }
+
+ @Test
+ void fromStringParsesSolarizedWithUnderscore() {
+ assertEquals(ColorScheme.SOLARIZED_DARK,
ColorScheme.fromString("solarized_dark"));
+ assertEquals(ColorScheme.SOLARIZED_DARK,
ColorScheme.fromString("SOLARIZED_DARK"));
+ assertEquals(ColorScheme.SOLARIZED_LIGHT,
ColorScheme.fromString("solarized_light"));
+ assertEquals(ColorScheme.SOLARIZED_LIGHT,
ColorScheme.fromString("SOLARIZED_LIGHT"));
+ }
+
+ @Test
+ void fromStringReturnsNullForInvalidValues() {
+ assertNull(ColorScheme.fromString(null));
+ assertNull(ColorScheme.fromString(""));
+ assertNull(ColorScheme.fromString("invalid"));
+ assertNull(ColorScheme.fromString("auto"));
+ }
+
+ @Test
+ void lightSchemeHasDarkerColorsForLightBackgrounds() {
+ // Primary text should be black (0) on light, white-ish on dark
+ assertEquals(0, ColorScheme.LIGHT.primaryColor());
+ assertEquals(252, ColorScheme.DARK.primaryColor());
+ }
+
+ @Test
+ void solarizedUsesConsistentAccentColors() {
+ // Solarized uses the same accent colors for both dark and light themes
+ assertEquals(ColorScheme.SOLARIZED_DARK.errorColor(),
ColorScheme.SOLARIZED_LIGHT.errorColor());
+ assertEquals(ColorScheme.SOLARIZED_DARK.successColor(),
ColorScheme.SOLARIZED_LIGHT.successColor());
+ assertEquals(ColorScheme.SOLARIZED_DARK.warningColor(),
ColorScheme.SOLARIZED_LIGHT.warningColor());
+ assertEquals(ColorScheme.SOLARIZED_DARK.infoColor(),
ColorScheme.SOLARIZED_LIGHT.infoColor());
+ }
+}