Skip to content

Commit

Permalink
Fix last line not displayed when scrolling using Display, fixes #737 (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Nov 8, 2021
1 parent 997496e commit f89e28a
Show file tree
Hide file tree
Showing 4 changed files with 2,185 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ private void poke(int y, int x, long[] s) {
int max = s.length;
while (cur < max) {
int nb = Math.min(width - x, max - cur);
System.arraycopy(s, 0, screen[y++], x, nb);
System.arraycopy(s, cur, screen[y++], x, nb);
x = 0;
cur += nb;
}
Expand Down
2 changes: 1 addition & 1 deletion terminal/src/main/java/org/jline/utils/Display.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public void update(List<AttributedString> newLines, int targetCursorPos, boolean

int lineIndex = 0;
int currentPos = 0;
int numLines = Math.max(oldLines.size(), newLines.size());
int numLines = Math.min(rows, Math.max(oldLines.size(), newLines.size()));
boolean wrapNeeded = false;
while (lineIndex < numLines) {
AttributedString oldLine =
Expand Down
211 changes: 211 additions & 0 deletions terminal/src/test/java/org/jline/utils/DisplayTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Copyright (c) 2021, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package org.jline.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import org.jline.terminal.Attributes;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.terminal.impl.LineDisciplineTerminal;
import org.junit.Test;

import static org.jline.utils.InfoCmp.Capability.enter_ca_mode;
import static org.jline.utils.InfoCmp.Capability.exit_ca_mode;
import static org.junit.Assert.assertEquals;

public class DisplayTest {

@Test
public void i737() throws IOException {
int rows = 10;
int cols = 25;
try (VirtualTerminal terminal = new VirtualTerminal("jline", "xterm", StandardCharsets.UTF_8, cols, rows)) {
Attributes savedAttributes = terminal.enterRawMode();
terminal.puts(enter_ca_mode);
int height = terminal.getHeight();

Display display = new Display(terminal, true);
display.resize(height, terminal.getWidth());

// Build Strings to displayed
List<AttributedString> lines1 = new ArrayList<>();
for (int i = 1; i < height + 1; i++) {
lines1.add(new AttributedString(String.format("%03d: %s", i, "Chaine de test...")));
}

List<AttributedString> lines2 = new ArrayList<>();
for (int i = 0; i < height; i++) {
lines2.add(new AttributedString(String.format("%03d: %s", i, "Chaine de test...")));
}

display.update(lines1, 0);

display.update(lines2, 0);

long[] screen = terminal.dump();
List<AttributedString> lines = new ArrayList<>();
for (int r = 0; r < rows; r++) {
AttributedStringBuilder sb = new AttributedStringBuilder();
for (int i = 0; i < cols; i++) {
sb.append((char) screen[i + cols * r]);
}
lines.add(sb.toAttributedString());
}
assertEquals("009: Chaine de test... ", lines.get(rows-1).toString());

terminal.setAttributes(savedAttributes);
terminal.puts(exit_ca_mode);
}
}

static class VirtualTerminal extends LineDisciplineTerminal {
private final ScreenTerminal virtual;
private final OutputStream masterInputOutput;
public VirtualTerminal(String name, String type, Charset encoding, int cols, int rows) throws IOException {
super(name, type, new DelegateOutputStream(), encoding);
setSize(new Size(cols, rows));
virtual = new ScreenTerminal(cols, rows);
((DelegateOutputStream) masterOutput).output = new MasterOutputStream();
masterInputOutput = new OutputStream() {
@Override
public void write(int b) throws IOException {
VirtualTerminal.this.processInputByte(b);
}
};
}

public long[] dump() {
long[] screen = new long[size.getRows() * size.getColumns()];
virtual.dump(screen, 0, 0, size.getRows(), size.getColumns(), null);
return screen;
}

private static class DelegateOutputStream extends OutputStream {
OutputStream output;

@Override
public void write(int b) throws IOException {
output.write(b);
}

@Override
public void write(byte[] b) throws IOException {
output.write(b);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
output.write(b, off, len);
}

@Override
public void flush() throws IOException {
output.flush();
}

@Override
public void close() throws IOException {
output.close();
}
}

private class MasterOutputStream extends OutputStream {
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final CharsetDecoder decoder = Charset.defaultCharset().newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);

@Override
public synchronized void write(int b) {
buffer.write(b);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
buffer.write(b, off, len);
}

@Override
public synchronized void flush() throws IOException {
int size = buffer.size();
if (size > 0) {
CharBuffer out;
for (; ; ) {
out = CharBuffer.allocate(size);
ByteBuffer in = ByteBuffer.wrap(buffer.toByteArray());
CoderResult result = decoder.decode(in, out, false);
if (result.isOverflow()) {
size *= 2;
} else {
buffer.reset();
buffer.write(in.array(), in.arrayOffset(), in.remaining());
break;
}
}
if (out.position() > 0) {
out.flip();
virtual.write(out);
masterInputOutput.write(virtual.read().getBytes());
}
}
}

@Override
public void close() throws IOException {
flush();
}
}
}

public static void main(String[] args) throws InterruptedException, IOException {

try (Terminal terminal = TerminalBuilder.builder().build()) {
Attributes savedAttributes = terminal.enterRawMode();
terminal.puts(enter_ca_mode);
int height = terminal.getHeight();

Display display = new Display(terminal, true);
display.resize(height, terminal.getWidth());

// Build Strings to displayed
List<AttributedString> lines1 = new ArrayList<>();
for (int i = 1; i < height + 1; i++) {
lines1.add(new AttributedString(String.format("%03d: %s", i, "Chaine de test...")));
}

List<AttributedString> lines2 = new ArrayList<>();
for (int i = 0; i < height; i++) {
lines2.add(new AttributedString(String.format("%03d: %s", i, "Chaine de test...")));
}

// Display with tempo
display.update(lines1, 0);
Thread.sleep(3000);

display.update(lines2, 0);
Thread.sleep(3000);

terminal.setAttributes(savedAttributes);
terminal.puts(exit_ca_mode);
}
}
}

0 comments on commit f89e28a

Please sign in to comment.