Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Ansi.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.codejive.twinkle.ansi;

import java.io.IOException;

public class Ansi {
public static final char ESC = '\u001B';

Expand Down Expand Up @@ -68,22 +70,26 @@ public static String style(Object... styles) {
if (styles == null || styles.length == 0) {
return "";
}
return style(new StringBuilder(), styles).toString();
try {
return style(new StringBuilder(), styles).toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static StringBuilder style(StringBuilder sb, Object... styles) {
public static Appendable style(Appendable appendable, Object... styles) throws IOException {
if (styles == null || styles.length == 0) {
return sb;
return appendable;
}
sb.append(CSI);
appendable.append(CSI);
for (int i = 0; i < styles.length; i++) {
sb.append(styles[i]);
appendable.append(styles[i].toString());
if (i < styles.length - 1) {
sb.append(";");
appendable.append(";");
}
}
sb.append("m");
return sb;
appendable.append("m");
return appendable;
}

public static String foreground(int index) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.codejive.twinkle.ansi;

import java.io.IOException;
import org.jspecify.annotations.NonNull;

public interface Printable {
/**
* Converts the object to an ANSI string, including ANSI escape codes for styles. This method
* resets the current style to default at the start of the string.
*
* @return The ANSI string representation of the object.
*/
@NonNull String toAnsiString();

/**
* Outputs the object as an ANSI string, including ANSI escape codes for styles. This method
* resets the current style to default at the start of the output.
*
* @param appendable The <code>Appendable</code> to write the ANSI output to.
* @return The <code>Appendable</code> passed as parameter.
*/
@NonNull Appendable toAnsi(Appendable appendable) throws IOException;

/**
* Converts the object to an ANSI string, including ANSI escape codes for styles. This method
* takes into account the provided current style to generate a result that is as efficient as
* possible in terms of ANSI codes.
*
* @param currentStyle The current style to start with.
* @return The ANSI string representation of the object.
*/
default @NonNull String toAnsiString(Style currentStyle) {
return toAnsiString(currentStyle.state());
}

/**
* Outputs the object as an ANSI string, including ANSI escape codes for styles. This method
* takes into account the provided current style to generate a result that is as efficient as
* possible in terms of ANSI codes.
*
* @param appendable The <code>Appendable</code> to write the ANSI output to.
* @param currentStyle The current style to start with.
* @return The <code>Appendable</code> passed as parameter.
*/
default @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle)
throws IOException {
return toAnsi(appendable, currentStyle.state());
}

/**
* Converts the object to an ANSI string, including ANSI escape codes for styles. This method
* takes into account the provided current style to generate a result that is as efficient as
* possible in terms of ANSI codes.
*
* @param currentStyleState The current style to start with.
* @return The ANSI string representation of the object.
*/
@NonNull String toAnsiString(long currentStyleState);

/**
* Outputs the object as an ANSI string, including ANSI escape codes for styles. This method
* takes into account the provided current style to generate a result that is as efficient as
* possible in terms of ANSI codes.
*
* @param appendable The <code>Appendable</code> to write the ANSI output to.
* @param currentStyleState The current style to start with.
* @return The <code>Appendable</code> passed as parameter.
*/
@NonNull Appendable toAnsi(Appendable appendable, long currentStyleState) throws IOException;
}
38 changes: 22 additions & 16 deletions twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.codejive.twinkle.ansi;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.NonNull;

public class Style {
public class Style implements Printable {
private final long state;

private static final long IDX_BOLD = 0;
Expand Down Expand Up @@ -347,27 +348,32 @@ public String toString() {
return sb.toString();
}

public String toAnsiString() {
@Override
public @NonNull String toAnsiString() {
return toAnsiString(UNSTYLED);
}

public StringBuilder toAnsiString(StringBuilder sb) {
return toAnsiString(sb, Style.UNSTYLED);
}

public String toAnsiString(Style currentStyle) {
return toAnsiString(currentStyle.state());
}

public StringBuilder toAnsiString(StringBuilder sb, Style currentStyle) {
return toAnsiString(sb, currentStyle.state());
@Override
public @NonNull Appendable toAnsi(Appendable appendable) throws IOException {
try {
return toAnsi(appendable, Style.UNSTYLED);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public String toAnsiString(long currentStyleState) {
return toAnsiString(new StringBuilder(), currentStyleState).toString();
@Override
public @NonNull String toAnsiString(long currentStyleState) {
try {
return toAnsi(new StringBuilder(), currentStyleState).toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public StringBuilder toAnsiString(StringBuilder sb, long currentStyleState) {
@Override
public @NonNull Appendable toAnsi(Appendable appendable, long currentStyleState)
throws IOException {
List<Object> styles = new ArrayList<>();
if ((currentStyleState & (F_BOLD | F_FAINT)) != (state & (F_BOLD | F_FAINT))) {
// First we switch to NORMAL to clear both BOLD and FAINT
Expand Down Expand Up @@ -426,6 +432,6 @@ public StringBuilder toAnsiString(StringBuilder sb, long currentStyleState) {
if ((currentStyleState & MASK_BG_COLOR) != (state & MASK_BG_COLOR)) {
styles.add(bgColor().toAnsiBgArgs());
}
return Ansi.style(sb, styles.toArray());
return Ansi.style(appendable, styles.toArray());
}
}
10 changes: 5 additions & 5 deletions twinkle-chart/src/test/java/examples/BarDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ private static void printSimpleBar() {
Panel pnl = Panel.of(20, 1);
Bar b = Bar.bar().setValue(42);
b.render(pnl);
System.out.println(pnl.toString());
System.out.println(pnl);
}

private static void printHorizontalBars() {
Panel pnl = Panel.of(20, 4);
FracBarConfig cfg = FracBarConfig.create();
renderHorizontal(pnl, cfg);
System.out.println(pnl.toString());
System.out.println(pnl);

cfg.direction(BarConfig.Direction.R2L);
renderHorizontal(pnl, cfg);
System.out.println(pnl.toString());
System.out.println(pnl);
}

private static void renderHorizontal(Panel pnl, FracBarConfig cfg) {
Expand All @@ -48,11 +48,11 @@ private static void printVerticalBars() {
Panel pnl = Panel.of(16, 8);
FracBarConfig cfg = FracBarConfig.create().direction(BarConfig.Direction.B2T);
renderVertical(pnl, cfg);
System.out.println(pnl.toString());
System.out.println(pnl);

cfg.direction(BarConfig.Direction.T2B);
renderVertical(pnl, cfg);
System.out.println(pnl.toString());
System.out.println(pnl);
}

private static void renderVertical(Panel pnl, FracBarConfig cfg) {
Expand Down
14 changes: 9 additions & 5 deletions twinkle-chart/src/test/java/examples/MathPlotFourDemo.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// java
package examples;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
import org.codejive.twinkle.ansi.Ansi;
import org.codejive.twinkle.ansi.Color;
Expand All @@ -15,7 +17,7 @@
import org.jspecify.annotations.NonNull;

public class MathPlotFourDemo {
public static void main(String[] args) throws InterruptedException {
public static void main(String[] args) throws InterruptedException, IOException {
Panel pnl = Panel.of(60, 40);
AnimatingMathPlot p1 = new AnimatingMathPlot(Size.of(30, 20), " Interfering Waves 1 ");
AnimatingMathPlot p2 = new AnimatingMathPlot(Size.of(30, 20), " Interfering Waves 2 ");
Expand All @@ -26,11 +28,12 @@ public static void main(String[] args) throws InterruptedException {
Canvas v3 = pnl.view(0, 20, 30, 20);
Canvas v4 = pnl.view(30, 20, 30, 20);

System.out.print(Ansi.hideCursor());
PrintWriter pout = new PrintWriter(System.out);
pout.print(Ansi.hideCursor());
try {
for (int i = 0; i < 400; i++) {
if (i > 0) {
System.out.print(Ansi.cursorMove(Ansi.CURSOR_PREV_LINE, pnl.size().height()));
pout.print(Ansi.cursorMove(Ansi.CURSOR_PREV_LINE, pnl.size().height()));
}

p1.update();
Expand All @@ -43,12 +46,13 @@ public static void main(String[] args) throws InterruptedException {
p3.render(v3);
p4.render(v4);

System.out.println(pnl.toAnsiString());
pnl.toAnsi(pout);
pout.println();

Thread.sleep(20);
}
} finally {
System.out.print(Ansi.showCursor());
pout.print(Ansi.showCursor());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package org.codejive.twinkle.core.text;

import org.codejive.twinkle.ansi.Printable;
import org.codejive.twinkle.ansi.Style;
import org.jspecify.annotations.NonNull;

public interface StyledBuffer extends StyledCharSequence {
public interface StyledBuffer extends StyledCharSequence, Printable {

char REPLACEMENT_CHAR = '\uFFFD';

Expand Down Expand Up @@ -35,36 +36,6 @@ default int putStringAt(int index, @NonNull Style style, @NonNull CharSequence s

@NonNull StyledBuffer resize(int newSize);

/**
* Converts the buffer to an ANSI string, including ANSI escape codes for styles. This method
* resets the current style to default at the start of the string.
*
* @return The ANSI string representation of the styled buffer.
*/
@NonNull String toAnsiString();

/**
* Converts the buffer to an ANSI string, including ANSI escape codes for styles. This method
* takes into account the provided current style to generate a result that is as efficient as
* possible in terms of ANSI codes.
*
* @param currentStyle The current style to start with.
* @return The ANSI string representation of the styled buffer.
*/
default @NonNull String toAnsiString(Style currentStyle) {
return toAnsiString(currentStyle.state());
}

/**
* Converts the buffer to an ANSI string, including ANSI escape codes for styles. This method
* takes into account the provided current style to generate a result that is as efficient as
* possible in terms of ANSI codes.
*
* @param currentStyle The current style to start with.
* @return The ANSI string representation of the styled buffer.
*/
@NonNull String toAnsiString(long currentStyle);

StyledBuffer EMPTY =
new StyledCodepointBuffer(0) {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.codejive.twinkle.core.text;

import java.io.IOException;
import org.codejive.twinkle.ansi.Ansi;
import org.codejive.twinkle.ansi.Style;
import org.jspecify.annotations.NonNull;
Expand Down Expand Up @@ -255,8 +256,17 @@ private boolean outside(int index, int length) {
// plus 20 extra for escape codes
int initialCapacity = length() + 20;
StringBuilder sb = new StringBuilder(initialCapacity);
sb.append(Ansi.STYLE_RESET);
return toAnsiString(sb, Style.UNSTYLED.state()).toString();
try {
return toAnsi(sb).toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public @NonNull Appendable toAnsi(Appendable appendable) throws IOException {
appendable.append(Ansi.STYLE_RESET);
return toAnsi(appendable, Style.UNSTYLED.state());
}

/**
Expand All @@ -278,22 +288,36 @@ private boolean outside(int index, int length) {
// plus 20 extra for escape codes
int initialCapacity = length() + 20;
StringBuilder sb = new StringBuilder(initialCapacity);
return toAnsiString(sb, styleState).toString();
try {
return toAnsi(sb, styleState).toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private @NonNull StringBuilder toAnsiString(StringBuilder sb, long lastStyleState) {
@Override
public @NonNull Appendable toAnsi(Appendable appendable, long lastStyleState)
throws IOException {
for (int i = 0; i < length(); i++) {
if (styleBuffer[i] != lastStyleState) {
Style style = Style.of(styleBuffer[i]);
style.toAnsiString(sb, lastStyleState);
style.toAnsi(appendable, lastStyleState);
lastStyleState = styleBuffer[i];
}
int cp = cpBuffer[i];
if (cp == '\0') {
cp = ' ';
}
sb.appendCodePoint(cp);
if (Character.isBmpCodePoint(cp)) {
appendable.append((char) cp);
} else if (Character.isValidCodePoint(cp)) {
appendable.append(Character.lowSurrogate(cp));
appendable.append(Character.highSurrogate(cp));
} else {
throw new IllegalArgumentException(
String.format("Not a valid Unicode code point: 0x%X", cp));
}
}
return sb;
return appendable;
}
}
Loading
Loading