diff --git a/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Ansi.java b/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Ansi.java index 2f7db5b..cf16542 100644 --- a/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Ansi.java +++ b/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Ansi.java @@ -53,6 +53,15 @@ public class Ansi { public static final int COLORS_RGB = 2; public static final int COLORS_INDEXED = 5; + // OSC 8 hyperlinks (BEL-terminated) + public static String osc8Open(String url) { + return "\u001B]8;;" + url + "\u0007"; + } + + public static String osc8Close() { + return "\u001B]8;;\u0007"; + } + public static String style(Object... styles) { if (styles == null || styles.length == 0) { return ""; @@ -100,19 +109,23 @@ public static String backgroundBright(int index) { } public static String foregroundIndexed(int index) { - return FOREGROUND_COLORS + ":" + COLORS_INDEXED + ":" + index; + // Standard SGR: 38;5; + return FOREGROUND_COLORS + ";" + COLORS_INDEXED + ";" + index; } public static String foregroundRgb(int r, int g, int b) { - return FOREGROUND_COLORS + ":" + COLORS_RGB + ":" + r + ":" + g + ":" + b; + // Standard SGR truecolor: 38;2;;; + return FOREGROUND_COLORS + ";" + COLORS_RGB + ";" + r + ";" + g + ";" + b; } public static String backgroundIndexed(int index) { - return BACKGROUND_COLORS + ":" + COLORS_INDEXED + ":" + index; + // Standard SGR: 48;5; + return BACKGROUND_COLORS + ";" + COLORS_INDEXED + ";" + index; } public static String backgroundRgb(int r, int g, int b) { - return BACKGROUND_COLORS + ":" + COLORS_RGB + ":" + r + ":" + g + ":" + b; + // Standard SGR truecolor: 48;2;;; + return BACKGROUND_COLORS + ";" + COLORS_RGB + ";" + r + ";" + g + ";" + b; } private Ansi() {} diff --git a/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java b/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java index f0edf9e..7ca0b72 100644 --- a/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java +++ b/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; + import org.jspecify.annotations.NonNull; public class Style { @@ -421,11 +422,24 @@ public StringBuilder toAnsiString(StringBuilder sb, long currentStyleState) { } } if ((currentStyleState & MASK_FG_COLOR) != (state & MASK_FG_COLOR)) { - styles.add(fgColor().toAnsiFg()); + styles.add(extractSgrParams(fgColor().toAnsiFg())); } if ((currentStyleState & MASK_BG_COLOR) != (state & MASK_BG_COLOR)) { - styles.add(bgColor().toAnsiBg()); + styles.add(extractSgrParams(bgColor().toAnsiBg())); } return Ansi.style(sb, styles.toArray()); } + + /** + * Converts a full SGR ANSI escape sequence (e.g. {@code ESC[38;2;255;0;0m}) into just the + * contained SGR parameters (e.g. {@code "38;2;255;0;0"}), so it can be safely embedded into + * another {@code ESC[...m} sequence without producing broken output. + */ + private static String extractSgrParams(String ansi) { + if (ansi == null || ansi.isEmpty()) return ""; + if (ansi.startsWith(Ansi.CSI) && ansi.endsWith("m")) { + return ansi.substring(Ansi.CSI.length(), ansi.length() - 1); + } + return ansi; + } } diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Console.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Console.java new file mode 100644 index 0000000..f9c94d0 --- /dev/null +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Console.java @@ -0,0 +1,660 @@ +package org.codejive.twinkle.core.text; + +import java.net.URI; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +import org.codejive.twinkle.ansi.Ansi; +import org.codejive.twinkle.ansi.Color; +import org.codejive.twinkle.ansi.Style; +import org.codejive.twinkle.core.widget.Panel; + +public final class Console { + private Console() {} + + // ---- Fluent builder (Span/Line -> Panel -> ANSI) ---- + + /** Entry point for building rich console output using a style stack. */ + public static Rich text() { + return new Rich(); + } + + public static final class Rich { + private final SpanLinesBuilder out = new SpanLinesBuilder(); + private final Deque