Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added a buffer for unfinished lines during progress bar printing. the lines will be completed by the next print, or way at the end when all progress bars are done. #1951

Merged
merged 8 commits into from
May 24, 2024
14 changes: 14 additions & 0 deletions src/org/rascalmpl/library/util/Monitor.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,18 @@ test bool simpleAsyncPrintTest() {
println("d");
jobEnd("job");
return true;
}

test bool unfinishedInputTest() {
jobStart("job", totalWork=26);
for (/<l:[a-z]>/ := "abcdefghijklmnopqrstuwvxyz") {
print(l); // no newline!
jobStep("job", "letter <l>", work=1);
if (arbInt(10) == 0) {
println(); // break it
}
}
// println(); // flush it
jobEnd("job");
return true;
}
129 changes: 125 additions & 4 deletions src/org/rascalmpl/repl/TerminalProgressBarMonitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

Expand Down Expand Up @@ -36,6 +38,13 @@
*/
private List<ProgressBar> bars = new LinkedList<>();

/**
* We also keep a list of currently unfinished lines (one for each thread).
* Since there are generally very few threads a simple list beats a hash-map in terms
* of memory allocation and possibly also lookup efficiency.
*/
private List<UnfinishedLine> unfinishedLines = new ArrayList<>(3);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L46 was not covered by tests

/**
* This writer is there to help with the encoding to what the terminal needs. It writes directly to the
* underlying stream.
Expand Down Expand Up @@ -109,6 +118,89 @@
}
}

private static class UnfinishedLine {
final long threadId;
private int curCapacity = 512;
private byte[] buffer = new byte[curCapacity];
private int curEnd = 0;

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L123 - L125 were not covered by tests

public UnfinishedLine() {
this.threadId = Thread.currentThread().getId();
}

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

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L127-L129

Added lines #L127 - L129 were not covered by tests

/**
* Adding input combines previously unfinished sentences with possible
* new (unfinished) sentences.
*
* The resulting buffer nevers contain any newline character.
*/
private void store(byte[] newInput, int offset, int len) {
// first ensure capacity of the array
if (curEnd + len >= curCapacity) {
curCapacity *= 2;
buffer = Arrays.copyOf(buffer, curCapacity);

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

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L140-L141

Added lines #L140 - L141 were not covered by tests
}

System.arraycopy(newInput, offset, buffer, curEnd, len);
curEnd += len;
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L144 - L146 were not covered by tests

public void write(byte[] n, OutputStream out) throws IOException {
write(n, 0, n.length, out);
}

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#L149-L150

Added lines #L149 - L150 were not covered by tests

/**
* Main workhorse looks for newline characters in the new input.
* - if there are newlines, than whatever is in the buffer can be flushed.
* - all the characters up to the last new new line are flushed immediately.
* - all the new characters after the last newline are buffered.
*/
public void write(byte[] n, int offset, int len, OutputStream out) throws IOException {
int lastNL = startOfLastLine(n, offset, len);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L159 was not covered by tests

if (lastNL == -1) {
store(n, offset, len);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L162 was not covered by tests
}
else {
flush(out);
out.write(n, offset, lastNL + 1);
store(n, offset, lastNL - offset);

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

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L165-L167

Added lines #L165 - L167 were not covered by tests
DavyLandman marked this conversation as resolved.
Show resolved Hide resolved
}
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L169 was not covered by tests

/**
* This empties the current buffer onto the stream,
* and resets the cursor.
*/
private void flush(OutputStream out) throws IOException {
if (curEnd != 0) {
out.write(buffer, 0, curEnd);
curEnd = 0;

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

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L177-L178

Added lines #L177 - L178 were not covered by tests
}
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L180 was not covered by tests

/**
* Prints whatever is the last line in the buffer,
* and adds a newline.
*/
public void flushLastLine(OutputStream out) throws IOException {
if (curEnd != 0) {
flush(out);
out.write('\n');

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#L188-L189

Added lines #L188 - L189 were not covered by tests
}
}

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L191 was not covered by tests

private int startOfLastLine(byte[] buffer, int offset, int len) {
for (int i = offset + len - 1; i >= 0; i--) {
DavyLandman marked this conversation as resolved.
Show resolved Hide resolved
if (buffer[i] == '\n') {
return i;

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L196 was not covered by tests
}
}

return -1;

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L200 was not covered by tests
}
}

/**
* Represents one currently running progress bar
*/
Expand Down Expand Up @@ -234,7 +326,7 @@
+ " "
;

writer.println("\r" + line); // note this puts us one line down
writer.println(line); // note this puts us one line down

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L329 was not covered by tests
}

private String threadLabel() {
Expand Down Expand Up @@ -395,6 +487,21 @@
.filter(b -> b.threadId == Thread.currentThread().getId())
.filter(b -> b.name.equals(name)).findFirst().orElseGet(() -> null);
}

private UnfinishedLine findUnfinishedLine() {
UnfinishedLine before = unfinishedLines.stream()

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L492 was not covered by tests
.filter(l -> l.threadId == Thread.currentThread().getId())
.findAny()
.orElse(null);

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

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L494-L495

Added lines #L494 - L495 were not covered by tests

if (before == null) {
UnfinishedLine l = new UnfinishedLine();
jurgenvinju marked this conversation as resolved.
Show resolved Hide resolved
unfinishedLines.add(l);
before = l;

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

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L498-L500

Added lines #L498 - L500 were not covered by tests
}

return before;

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L503 was not covered by tests
}

@Override
public synchronized void jobStart(String name, int workShare, int totalWork) {
Expand Down Expand Up @@ -483,12 +590,18 @@
* Here we make sure the progress bars are gone just before
* someone wants to print in the console. When the printing
* is ready, we simply add our own progress bars again.
*
* Special cases handle when there are unfinished lines in play.
* This code guarantees that printBars is only called when the cursor
* is before the first character of a line.
*/
@Override
public synchronized void write(byte[] b) throws IOException {
if (!bars.isEmpty()) {
eraseBars();
out.write(b);

findUnfinishedLine().write(b, out);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L603 was not covered by tests

printBars();
}
else {
Expand All @@ -505,7 +618,7 @@
public synchronized void write(byte[] b, int off, int len) throws IOException {
if (!bars.isEmpty()) {
eraseBars();
out.write(b, off, len);
findUnfinishedLine().write(b, off, len, out);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L621 was not covered by tests
printBars();
}
else {
Expand All @@ -522,7 +635,7 @@
public synchronized void write(int b) throws IOException {
if (!bars.isEmpty()) {
eraseBars();
out.write(b);
findUnfinishedLine().write(new byte[] { (byte) b }, out);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L638 was not covered by tests
printBars();
}
else {
Expand All @@ -541,6 +654,14 @@
}

bars.clear();
for (UnfinishedLine l : unfinishedLines) {
try {
l.flushLastLine(out);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L659 was not covered by tests
}
catch (IOException e) {

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L661 was not covered by tests
// might happen if the terminal crashes before we stop running
}
}

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

View check run for this annotation

Codecov / codecov/patch

src/org/rascalmpl/repl/TerminalProgressBarMonitor.java#L663-L664

Added lines #L663 - L664 were not covered by tests
writer.write(ANSI.showCursor());
writer.flush();
}
Expand Down
1 change: 0 additions & 1 deletion src/org/rascalmpl/test/infrastructure/TestFramework.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.util.Set;

import org.junit.After;
import org.rascalmpl.debug.IRascalMonitor;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.env.GlobalEnvironment;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
Expand Down
2 changes: 0 additions & 2 deletions src/org/rascalmpl/uri/file/UNCResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.regex.Pattern;

import io.usethesource.vallang.ISourceLocation;

/**
Expand Down
2 changes: 0 additions & 2 deletions test/org/rascalmpl/MatchFingerprintTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import org.rascalmpl.interpreter.env.GlobalEnvironment;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
import org.rascalmpl.test.infrastructure.RascalJUnitTestRunner;
import org.rascalmpl.test.infrastructure.TestFramework;

import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IInteger;
import io.usethesource.vallang.ISourceLocation;
Expand Down
Loading