Skip to content

Commit

Permalink
had to write some ANSI voodoo to test if a terminal can print unicode…
Browse files Browse the repository at this point in the history
… or not
  • Loading branch information
jurgenvinju committed Apr 4, 2024
1 parent e50a9ab commit 15dc436
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 23 deletions.
91 changes: 74 additions & 17 deletions src/org/rascalmpl/repl/TerminalProgressBarMonitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import java.io.FilterOutputStream;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.Instant;
Expand Down Expand Up @@ -45,27 +47,56 @@ public class TerminalProgressBarMonitor extends FilterOutputStream implements IR
*/
private final int lineWidth;

/**
* For portability it is important to encode according to the needs of the actual terminal.
*/
private final String encoding;
private final boolean unicodeEnabled;

/**
* Will make everything slow, but easier to spot mistakes
*/
private final boolean debug = false;

Check warning on line 55 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L55

Added line #L55 was not covered by tests

@SuppressWarnings("resource")
public TerminalProgressBarMonitor(OutputStream out, Terminal tm) {
public TerminalProgressBarMonitor(OutputStream out, InputStream in, Terminal tm) {
super(out);
this.encoding = tm.getOutputEncoding() != null ? tm.getOutputEncoding() : "UTF8";
PrintWriter theWriter = new PrintWriter(out, true, Charset.forName(encoding));
PrintWriter theWriter = new PrintWriter(out, true, Charset.forName("UTF8"));
this.writer = debug ? new PrintWriter(new AlwaysFlushAlwaysShowCursor(theWriter)) : theWriter;
this.lineWidth = tm.getWidth();

this.unicodeEnabled = testUnicodeEnabled(in, tm);

Check warning on line 63 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L59-L63

Added lines #L59 - L63 were not covered by tests
assert System.console() != null: "interactive progress bar needs a terminal, and we should not print this into a file anyway.";
}

Check warning on line 65 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L65

Added line #L65 was not covered by tests

private boolean testUnicodeEnabled(InputStream in, Terminal tm) {
try {
// print the current position
int pos = ANSI.getCursorPosition(writer, in);

Check warning on line 70 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L70

Added line #L70 was not covered by tests

// now print a unicode character
writer.write("あ");

Check warning on line 73 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L73

Added line #L73 was not covered by tests

// get the new position
int newPos = ANSI.getCursorPosition(writer, in);

Check warning on line 76 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L76

Added line #L76 was not covered by tests

int diff = newPos - pos;

Check warning on line 78 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L78

Added line #L78 was not covered by tests
// should be only one character
boolean isUnicode = (diff == 2);

// clean up
while (diff-- > 0) {
writer.write(ANSI.delete());

Check warning on line 84 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L84

Added line #L84 was not covered by tests
}
writer.flush();

Check warning on line 86 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L86

Added line #L86 was not covered by tests

return isUnicode;

Check warning on line 88 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L88

Added line #L88 was not covered by tests
}
catch (IOException e) {
return false;

Check warning on line 91 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L90-L91

Added lines #L90 - L91 were not covered by tests
}
finally {

Check warning on line 93 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L93

Added line #L93 was not covered by tests

}

Check warning on line 95 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L95

Added line #L95 was not covered by tests
}



/**
* Use this for debugging terminal cursor movements, step by step.
*/
Expand Down Expand Up @@ -94,9 +125,7 @@ public void write(String str, int off, int len) throws IOException {
out.write(str, off, len);
out.write(ANSI.showCursor());
out.flush();
}


}

Check warning on line 128 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L125-L128

Added lines #L125 - L128 were not covered by tests
}

/**
Expand All @@ -110,12 +139,14 @@ private class ProgressBar {
private int current = 0;
private int previousWidth = 0;
private int doneWidth = 0;
private final int barWidth = lineWidth - "☐ ".length() - " 🕐 00:00:00.000 ".length();
private final int barWidthUnicode = lineWidth - "☐ ".length() - " 🕐 00:00:00.000 ".length();
private final int barWidthAscii = lineWidth - "? ".length() - " - 00:00:00.000 ".length();

Check warning on line 143 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L139-L143

Added lines #L139 - L143 were not covered by tests
private final Instant startTime;
private Duration duration;
private String message = "";
private int stepper = 1;
private final String[] clocks = new String[] {"🕐" , "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕛"};
private final String[] twister = new String[] {"|" , "/", "-", "\\", "|", "/", "-", "\\"};
public int nesting = 0;

Check warning on line 150 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L146-L150

Added lines #L146 - L150 were not covered by tests

ProgressBar(String name, int max) {
Expand Down Expand Up @@ -155,10 +186,10 @@ int newWidth() {
if (max != 0) {
current = Math.min(max, current); // for robustness sake
var partDone = (current * 1.0) / (max * 1.0);
return (int) Math.floor(barWidth * partDone);
return (int) Math.floor(barWidthUnicode * partDone);

Check warning on line 189 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L187-L189

Added lines #L187 - L189 were not covered by tests
}
else {
return barWidth % stepper;
return barWidthUnicode % stepper;

Check warning on line 192 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L192

Added line #L192 was not covered by tests
}
}

Expand All @@ -170,19 +201,23 @@ void write() {
doneWidth = newWidth();

Check warning on line 201 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L200-L201

Added lines #L200 - L201 were not covered by tests

// var overWidth = barWidth - doneWidth;
var done = current >= max ? "☑ " : "☐ ";
var done = unicodeEnabled
? (current >= max ? "☑ " : "☐ ")
: (current >= max ? "X " : "O ")
;

// capitalize
var msg = message.substring(0, 1).toUpperCase() + message.substring(1, message.length());

Check warning on line 210 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L210

Added line #L210 was not covered by tests

// fill up and cut off:
msg = threadLabel() + msg;
msg = (msg + " ".repeat(Math.max(0, barWidth - msg.length()))).substring(0, barWidth);
msg = (msg + " ".repeat(Math.max(0, barWidthUnicode - msg.length()))).substring(0, barWidthUnicode);

Check warning on line 214 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L213-L214

Added lines #L213 - L214 were not covered by tests

// split
var barWidth = unicodeEnabled ? barWidthUnicode : barWidthAscii;
var frontPart = msg.substring(0, doneWidth);
var backPart = msg.substring(doneWidth, msg.length());

Check warning on line 219 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L218-L219

Added lines #L218 - L219 were not covered by tests
var clock = clocks[stepper % clocks.length];
var clock = unicodeEnabled ? clocks[stepper % clocks.length] : twister[stepper % twister.length];

if (barWidth < 1) {
return; // robustness against very small screens. At least don't throw bounds exceptions

Check warning on line 223 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L223

Added line #L223 was not covered by tests
Expand Down Expand Up @@ -261,10 +296,32 @@ private void eraseBars() {
* ANSI escape codes convenience functions
*/
private static class ANSI {
static int getCursorPosition(PrintWriter writer, InputStream in) throws IOException {
writer.write(ANSI.printCursorPosition());
writer.flush();

Check warning on line 301 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L300-L301

Added lines #L300 - L301 were not covered by tests

byte[] col = new byte[32];
int len = in.read(col);
String echo = new String(col, 0, len, "UTF8");

Check warning on line 305 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L303-L305

Added lines #L303 - L305 were not covered by tests

// terminal responds with ESC[n;mR, where n is the row and m is the column.
echo = echo.split(";")[1]; // take the column part
echo = echo.substring(0, echo.length() - 1); // remove the last R
return Integer.parseInt(echo);

Check warning on line 310 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L308-L310

Added lines #L308 - L310 were not covered by tests
}

public static String delete() {
return "\u001B[D\u001B[K";

Check warning on line 314 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L314

Added line #L314 was not covered by tests
}

static String moveUp(int n) {
return "\u001B[" + n + "F";

Check warning on line 318 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L318

Added line #L318 was not covered by tests
}

public static String printCursorPosition() {
return "\u001B[6n";

Check warning on line 322 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L322

Added line #L322 was not covered by tests
}

public static String darkBackground() {
return "\u001B[48;5;242m";

Check warning on line 326 in src/org/rascalmpl/repl/TerminalProgressBarMonitor.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L326

Added line #L326 was not covered by tests
}
Expand Down
2 changes: 1 addition & 1 deletion src/org/rascalmpl/shell/RascalShell.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public static void main(String[] args) throws IOException {

IRascalMonitor monitor
= System.console() != null
? new TerminalProgressBarMonitor(System.out, term)
? new TerminalProgressBarMonitor(System.out, System.in, term)
: new ConsoleRascalMonitor();

Check warning on line 74 in src/org/rascalmpl/shell/RascalShell.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/shell/RascalShell.java#L73-L74

Added lines #L73 - L74 were not covered by tests

IDEServices services = new BasicIDEServices(new PrintWriter(System.err), monitor);

Check warning on line 76 in src/org/rascalmpl/shell/RascalShell.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/shell/RascalShell.java#L76

Added line #L76 was not covered by tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
package org.rascalmpl.test.infrastructure;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
Expand Down Expand Up @@ -58,7 +57,7 @@
*/
public class RascalJUnitParallelRecursiveTestRunner extends Runner {
private final static IRascalMonitor monitor = System.console() != null
? new TerminalProgressBarMonitor(System.out, TerminalFactory.get())
? new TerminalProgressBarMonitor(System.out, System.in, TerminalFactory.get())

Check warning on line 60 in src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java#L60

Added line #L60 was not covered by tests
: new NullRascalMonitor();

private final int numberOfWorkers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public class RascalJUnitTestRunner extends Runner {

Terminal tm = TerminalFactory.get();
IRascalMonitor monitor = System.console() != null
? new TerminalProgressBarMonitor(System.out, tm)
? new TerminalProgressBarMonitor(System.out, System.in, tm)

Check warning on line 71 in src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java#L71

Added line #L71 was not covered by tests
: new NullRascalMonitor();

var outStream = System.console() != null
Expand Down
3 changes: 1 addition & 2 deletions src/org/rascalmpl/test/infrastructure/TestFramework.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

import org.junit.After;
import org.rascalmpl.debug.IRascalMonitor;
import org.rascalmpl.interpreter.ConsoleRascalMonitor;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.NullRascalMonitor;
import org.rascalmpl.interpreter.env.GlobalEnvironment;
Expand All @@ -52,7 +51,7 @@

public class TestFramework {
private final static IRascalMonitor monitor = System.console() != null
? new TerminalProgressBarMonitor(System.out, TerminalFactory.get())
? new TerminalProgressBarMonitor(System.out, System.in, TerminalFactory.get())

Check warning on line 54 in src/org/rascalmpl/test/infrastructure/TestFramework.java

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/test/infrastructure/TestFramework.java#L54

Added line #L54 was not covered by tests
: new NullRascalMonitor();

private final static Evaluator evaluator;
Expand Down

0 comments on commit 15dc436

Please sign in to comment.