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
10 changes: 5 additions & 5 deletions twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Ansi.java
Original file line number Diff line number Diff line change
Expand Up @@ -282,16 +282,16 @@ public static String autoWrap(boolean enabled) {
return CSI + (enabled ? LINE_WRAP_ON : LINE_WRAP_OFF);
}

public static String linkStart(String url) {
return OSC + "8;;" + url + OSC_END;
public static String link(String url) {
return OSC + HYPERLINK + ";" + url + OSC_END;
}

public static String linkStart(String url, String id) {
return OSC + "8;id=" + id + ";" + url + OSC_END;
public static String link(String url, String id) {
return OSC + HYPERLINK + "id=" + id + ";" + url + OSC_END;
}

public static String linkEnd() {
return OSC + "8;;" + OSC_END;
return OSC + HYPERLINK + ";" + OSC_END;
}

private Ansi() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public class Constants {
public static final String CSI = ESC + "["; // Control Sequence Introducer
public static final String OSC = ESC + "]"; // Operating System Command
public static final String OSC_END = "\u0007"; // Bell character
public static final String OSC_END_ALT = ESC + "\\"; // String Terminator (ST)

// Style codes
public static final int RESET = 0; // Reset all attributes
Expand Down Expand Up @@ -83,4 +84,6 @@ public class Constants {

public static final String LINE_WRAP_ON = "=7h";
public static final String LINE_WRAP_OFF = "=7l";

public static final String HYPERLINK = "8;";
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public Fluent link(String text, String url, String id) {
* @return this Fluent instance for chaining
*/
public Fluent url(String url) {
return append(Ansi.linkStart(url));
return append(Ansi.link(url));
}

/**
Expand All @@ -240,7 +240,7 @@ public Fluent url(String url) {
* @return this Fluent instance for chaining
*/
public Fluent url(String url, String id) {
return append(Ansi.linkStart(url, id));
return append(Ansi.link(url, id));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

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.
* Converts the object to an ANSI string, including ANSI escape codes for styles and links. This
* method resets the current style to default at the start of the string.
*
* @return The ANSI string representation of the object.
*/
Expand All @@ -15,8 +15,8 @@ public interface Printable {
}

/**
* 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.
* Outputs the object as an ANSI string, including ANSI escape codes for styles and links. 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.
Expand All @@ -26,9 +26,9 @@ public interface Printable {
}

/**
* 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.
* Converts the object to an ANSI string, including ANSI escape codes for styles and links. 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.
Expand All @@ -38,9 +38,9 @@ public interface Printable {
}

/**
* 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.
* Outputs the object as an ANSI string, including ANSI escape codes for styles and links. 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.
Expand Down
105 changes: 90 additions & 15 deletions twinkle-text/src/main/java/org/codejive/twinkle/text/Buffer.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.codejive.twinkle.text;

import java.io.IOException;
import java.util.Objects;
import org.codejive.twinkle.ansi.Ansi;
import org.codejive.twinkle.ansi.Style;
import org.codejive.twinkle.ansi.util.Printable;
import org.codejive.twinkle.text.io.BufferWriter;
import org.codejive.twinkle.text.io.PrintBufferWriter;
import org.codejive.twinkle.text.util.Hyperlink;
import org.codejive.twinkle.text.util.Rect;
import org.codejive.twinkle.text.util.Size;
import org.codejive.twinkle.text.util.StyledIterator;
Expand Down Expand Up @@ -43,19 +45,26 @@ protected static class InternalBuffers {
public final int[][] cpBuffer;
public final String[][] graphemeBuffer;
public final long[][] styleBuffer;
public final Hyperlink[][] linkBuffer;
public final @NonNull Size size;

public InternalBuffers(@NonNull Size size) {
this(
new int[size.height()][size.width()],
new String[size.height()][size.width()],
new long[size.height()][size.width()]);
new long[size.height()][size.width()],
new Hyperlink[size.height()][size.width()]);
}

public InternalBuffers(int[][] cpBuffer, String[][] graphemeBuffer, long[][] styleBuffer) {
public InternalBuffers(
int[][] cpBuffer,
String[][] graphemeBuffer,
long[][] styleBuffer,
Hyperlink[][] linkBuffer) {
this.cpBuffer = cpBuffer;
this.graphemeBuffer = graphemeBuffer;
this.styleBuffer = styleBuffer;
this.linkBuffer = linkBuffer;
this.size = Size.of(styleBuffer[0].length, styleBuffer.length);
}

Expand Down Expand Up @@ -149,6 +158,12 @@ private void copyData(
targetBuffers.styleBuffer[targetYPos],
targetX,
copyWidth);
System.arraycopy(
linkBuffer[sourceY],
sourceLeft,
targetBuffers.linkBuffer[targetYPos],
targetX,
copyWidth);
}
}

Expand Down Expand Up @@ -177,6 +192,8 @@ private void overlayData(
graphemeBuffer[sourceY][sourceX];
targetBuffers.styleBuffer[targetYPos][targetXPos] =
styleBuffer[sourceY][sourceX];
targetBuffers.linkBuffer[targetYPos][targetXPos] =
linkBuffer[sourceY][sourceX];
}
}
}
Expand All @@ -192,9 +209,11 @@ private void overlayData(
int[][] newCpBuffer = new int[newHeight][newWidth];
String[][] newGraphemeBuffer = new String[newHeight][newWidth];
long[][] newStyleBuffer = new long[newHeight][newWidth];
Hyperlink[][] newLinkBuffer = new Hyperlink[newHeight][newWidth];

InternalBuffers newBuffers =
new InternalBuffers(newCpBuffer, newGraphemeBuffer, newStyleBuffer);
new InternalBuffers(
newCpBuffer, newGraphemeBuffer, newStyleBuffer, newLinkBuffer);
copyTo(newBuffers, Rect.of(newWidth, newHeight), 0, 0, null);

return newBuffers;
Expand Down Expand Up @@ -354,7 +373,8 @@ public void putAt(int x, int y, char c, PrintOption... options) {
c = REPLACEMENT_CHAR;
}
Style style = opt(options, StylePrintOption.class, StylePrintOption.UNSTYLED).style();
setCharAt_(x, y, style.state(), c, null);
Hyperlink link = opt(options, LinkPrintOption.class, LinkPrintOption.NONE).hyperlink();
setCharAt_(x, y, style.state(), c, null, link);
}

@Override
Expand All @@ -363,7 +383,8 @@ public void putAt(int x, int y, int cp, PrintOption... options) {
return;
}
Style style = opt(options, StylePrintOption.class, StylePrintOption.UNSTYLED).style();
setCharAt_(x, y, style.state(), cp, null);
Hyperlink link = opt(options, LinkPrintOption.class, LinkPrintOption.NONE).hyperlink();
setCharAt_(x, y, style.state(), cp, null, link);
}

@Override
Expand All @@ -375,7 +396,8 @@ public void putAt(int x, int y, @NonNull CharSequence grapheme, PrintOption... o
return;
}
Style style = opt(options, StylePrintOption.class, StylePrintOption.UNSTYLED).style();
setCharAt_(x, y, style.state(), -1, grapheme.toString());
Hyperlink link = opt(options, LinkPrintOption.class, LinkPrintOption.NONE).hyperlink();
setCharAt_(x, y, style.state(), -1, grapheme.toString(), link);
}

public enum SimplePrintOption implements PrintOption {
Expand All @@ -397,10 +419,48 @@ public Style style() {
}
}

public static StylePrintOption styleOpt(Style style) {
public static @NonNull StylePrintOption styleOpt(Style style) {
return new StylePrintOption(style);
}

public static class LinkPrintOption implements PrintOption {
private final Hyperlink hyperlink;

public static final LinkPrintOption NONE = new LinkPrintOption(null);

public LinkPrintOption(@NonNull String url, String id) {
this.hyperlink = Hyperlink.of(url, id);
}

public LinkPrintOption(Hyperlink hyperlink) {
this.hyperlink = hyperlink;
}

public String url() {
return hyperlink != null ? hyperlink.url : null;
}

public String id() {
return hyperlink != null ? hyperlink.id : null;
}

public Hyperlink hyperlink() {
return hyperlink;
}
}

public static @NonNull LinkPrintOption linkOpt(@NonNull String url) {
return new LinkPrintOption(url, null);
}

public static @NonNull LinkPrintOption linkOpt(@NonNull String url, String id) {
return new LinkPrintOption(url, id);
}

public static @NonNull LinkPrintOption linkOpt(@NonNull Hyperlink hyperlink) {
return new LinkPrintOption(hyperlink);
}

public static class TransparencyPrintOption implements PrintOption {
private final String transparentCharacters;

Expand Down Expand Up @@ -460,15 +520,16 @@ public void printAt(int x, int y, @NonNull StyledIterator iter, PrintOption... o
continue;
}
Style style = iter.style();
Hyperlink link = iter.link();
int width = iter.width();
if (curX <= rect.right()) {
if (iter.isComplex()) {
setCharAt_(curX, curY, style.state(), -1, iter.sequence());
setCharAt_(curX, curY, style.state(), -1, iter.sequence(), link);
} else {
if (transparency.indexOf(cp) >= 0) {
width = 1;
} else {
setCharAt_(curX, curY, style.state(), cp, null);
setCharAt_(curX, curY, style.state(), cp, null, link);
}
}
}
Expand Down Expand Up @@ -507,7 +568,7 @@ private void setSkipAt(int x, int y) {
}
boolean isWide = (isWideAt(x, y));
// Set skip state to indicate cell is occupied by wide character from previous cell
setCellAt(x, y, -1, -1, null);
setCellAt(x, y, -1, -1, null, null);
if (isWide) {
// Clear the next cell's skip state if this cell contained a wide character
clearAt(x + 1, y);
Expand All @@ -528,7 +589,7 @@ public void clearAt(int x, int y) {
}

private void clearAt_(int x, int y) {
setCellAt(x, y, Style.F_UNSTYLED, '\0', null);
setCellAt(x, y, Style.F_UNSTYLED, '\0', null, null);
}

/**
Expand All @@ -550,14 +611,15 @@ public boolean isWideAt(int x, int y) {
return isWide;
}

private void setCharAt_(int x, int y, long styleState, int cp, String grapheme) {
private void setCharAt_(
int x, int y, long styleState, int cp, String grapheme, Hyperlink link) {
// Handle wide character overlap to the left of this cell
if (shouldSkipAt(x, y)) {
// The previous cell contains a wide character that overlaps this cell
clearAt(x - 1, y);
}

setCellAt(x, y, styleState, cp, grapheme);
setCellAt(x, y, styleState, cp, grapheme, link);

// Set a skip cell to the right if this is a wide character
boolean isWide = (grapheme != null) ? Unicode.isWide(grapheme) : Unicode.isWide(cp);
Expand All @@ -566,10 +628,11 @@ private void setCharAt_(int x, int y, long styleState, int cp, String grapheme)
}
}

private void setCellAt(int x, int y, long styleState, int cp, String grapheme) {
private void setCellAt(int x, int y, long styleState, int cp, String grapheme, Hyperlink link) {
buffers.cpBuffer[y][x] = cp;
buffers.graphemeBuffer[y][x] = grapheme;
buffers.styleBuffer[y][x] = styleState;
buffers.linkBuffer[y][x] = link;
}

/**
Expand Down Expand Up @@ -704,7 +767,7 @@ public String toString() {

/**
* Get a string representation of the content of the buffer within the specified rectangle. The
* output will contain only plain text without any style information.
* output will contain only plain text without any style information or links.
*
* @param rect the rectangle defining the area of the buffer to convert to a string
* @return a string representation of the content within the specified rectangle
Expand Down Expand Up @@ -751,6 +814,7 @@ public String toString(@NonNull Rect rect) {

public @NonNull Appendable toAnsi(
@NonNull Rect rect, @NonNull Appendable appendable, @NonNull Style currentStyle) {
Hyperlink currentLink = null;
if (currentStyle == Style.UNKNOWN) {
currentStyle = Style.DEFAULT;
appendStr(appendable, Ansi.STYLE_RESET);
Expand All @@ -766,6 +830,17 @@ public String toString(@NonNull Rect rect) {
style.toAnsi(appendable, currentStyle);
currentStyle = Style.of(buffers.styleBuffer[y][x]);
}
if (!Objects.equals(buffers.linkBuffer[y][x], currentLink)) {
Hyperlink link = buffers.linkBuffer[y][x];
if (currentLink != null) {
// Emit the end of the current hyperlink
Hyperlink.END.toAnsi(appendable);
}
if (link != null) {
link.toAnsi(appendable);
}
currentLink = link;
}
if (x == limitedRect.right() && isWideAt(x, y)) {
// Don't attempt to render a wide character if it would overflow the right
// edge of the buffer, as the wide character will be truncated and not
Expand Down
Loading
Loading