Skip to content

Commit f89e28a

Browse files
authoredNov 8, 2021
Fix last line not displayed when scrolling using Display, fixes #737 (#739)
1 parent 997496e commit f89e28a

File tree

4 files changed

+2185
-2
lines changed

4 files changed

+2185
-2
lines changed
 

‎builtins/src/main/java/org/jline/builtins/ScreenTerminal.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private void poke(int y, int x, long[] s) {
245245
int max = s.length;
246246
while (cur < max) {
247247
int nb = Math.min(width - x, max - cur);
248-
System.arraycopy(s, 0, screen[y++], x, nb);
248+
System.arraycopy(s, cur, screen[y++], x, nb);
249249
x = 0;
250250
cur += nb;
251251
}

‎terminal/src/main/java/org/jline/utils/Display.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public void update(List<AttributedString> newLines, int targetCursorPos, boolean
179179

180180
int lineIndex = 0;
181181
int currentPos = 0;
182-
int numLines = Math.max(oldLines.size(), newLines.size());
182+
int numLines = Math.min(rows, Math.max(oldLines.size(), newLines.size()));
183183
boolean wrapNeeded = false;
184184
while (lineIndex < numLines) {
185185
AttributedString oldLine =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Copyright (c) 2021, the original author or authors.
3+
*
4+
* This software is distributable under the BSD license. See the terms of the
5+
* BSD license in the documentation provided with this software.
6+
*
7+
* https://opensource.org/licenses/BSD-3-Clause
8+
*/
9+
package org.jline.utils;
10+
11+
import java.io.ByteArrayOutputStream;
12+
import java.io.IOException;
13+
import java.io.OutputStream;
14+
import java.nio.ByteBuffer;
15+
import java.nio.CharBuffer;
16+
import java.nio.charset.Charset;
17+
import java.nio.charset.CharsetDecoder;
18+
import java.nio.charset.CoderResult;
19+
import java.nio.charset.CodingErrorAction;
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
24+
import org.jline.terminal.Attributes;
25+
import org.jline.terminal.Size;
26+
import org.jline.terminal.Terminal;
27+
import org.jline.terminal.TerminalBuilder;
28+
import org.jline.terminal.impl.LineDisciplineTerminal;
29+
import org.junit.Test;
30+
31+
import static org.jline.utils.InfoCmp.Capability.enter_ca_mode;
32+
import static org.jline.utils.InfoCmp.Capability.exit_ca_mode;
33+
import static org.junit.Assert.assertEquals;
34+
35+
public class DisplayTest {
36+
37+
@Test
38+
public void i737() throws IOException {
39+
int rows = 10;
40+
int cols = 25;
41+
try (VirtualTerminal terminal = new VirtualTerminal("jline", "xterm", StandardCharsets.UTF_8, cols, rows)) {
42+
Attributes savedAttributes = terminal.enterRawMode();
43+
terminal.puts(enter_ca_mode);
44+
int height = terminal.getHeight();
45+
46+
Display display = new Display(terminal, true);
47+
display.resize(height, terminal.getWidth());
48+
49+
// Build Strings to displayed
50+
List<AttributedString> lines1 = new ArrayList<>();
51+
for (int i = 1; i < height + 1; i++) {
52+
lines1.add(new AttributedString(String.format("%03d: %s", i, "Chaine de test...")));
53+
}
54+
55+
List<AttributedString> lines2 = new ArrayList<>();
56+
for (int i = 0; i < height; i++) {
57+
lines2.add(new AttributedString(String.format("%03d: %s", i, "Chaine de test...")));
58+
}
59+
60+
display.update(lines1, 0);
61+
62+
display.update(lines2, 0);
63+
64+
long[] screen = terminal.dump();
65+
List<AttributedString> lines = new ArrayList<>();
66+
for (int r = 0; r < rows; r++) {
67+
AttributedStringBuilder sb = new AttributedStringBuilder();
68+
for (int i = 0; i < cols; i++) {
69+
sb.append((char) screen[i + cols * r]);
70+
}
71+
lines.add(sb.toAttributedString());
72+
}
73+
assertEquals("009: Chaine de test... ", lines.get(rows-1).toString());
74+
75+
terminal.setAttributes(savedAttributes);
76+
terminal.puts(exit_ca_mode);
77+
}
78+
}
79+
80+
static class VirtualTerminal extends LineDisciplineTerminal {
81+
private final ScreenTerminal virtual;
82+
private final OutputStream masterInputOutput;
83+
public VirtualTerminal(String name, String type, Charset encoding, int cols, int rows) throws IOException {
84+
super(name, type, new DelegateOutputStream(), encoding);
85+
setSize(new Size(cols, rows));
86+
virtual = new ScreenTerminal(cols, rows);
87+
((DelegateOutputStream) masterOutput).output = new MasterOutputStream();
88+
masterInputOutput = new OutputStream() {
89+
@Override
90+
public void write(int b) throws IOException {
91+
VirtualTerminal.this.processInputByte(b);
92+
}
93+
};
94+
}
95+
96+
public long[] dump() {
97+
long[] screen = new long[size.getRows() * size.getColumns()];
98+
virtual.dump(screen, 0, 0, size.getRows(), size.getColumns(), null);
99+
return screen;
100+
}
101+
102+
private static class DelegateOutputStream extends OutputStream {
103+
OutputStream output;
104+
105+
@Override
106+
public void write(int b) throws IOException {
107+
output.write(b);
108+
}
109+
110+
@Override
111+
public void write(byte[] b) throws IOException {
112+
output.write(b);
113+
}
114+
115+
@Override
116+
public void write(byte[] b, int off, int len) throws IOException {
117+
output.write(b, off, len);
118+
}
119+
120+
@Override
121+
public void flush() throws IOException {
122+
output.flush();
123+
}
124+
125+
@Override
126+
public void close() throws IOException {
127+
output.close();
128+
}
129+
}
130+
131+
private class MasterOutputStream extends OutputStream {
132+
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
133+
private final CharsetDecoder decoder = Charset.defaultCharset().newDecoder()
134+
.onMalformedInput(CodingErrorAction.REPLACE)
135+
.onUnmappableCharacter(CodingErrorAction.REPLACE);
136+
137+
@Override
138+
public synchronized void write(int b) {
139+
buffer.write(b);
140+
}
141+
142+
@Override
143+
public void write(byte[] b, int off, int len) throws IOException {
144+
buffer.write(b, off, len);
145+
}
146+
147+
@Override
148+
public synchronized void flush() throws IOException {
149+
int size = buffer.size();
150+
if (size > 0) {
151+
CharBuffer out;
152+
for (; ; ) {
153+
out = CharBuffer.allocate(size);
154+
ByteBuffer in = ByteBuffer.wrap(buffer.toByteArray());
155+
CoderResult result = decoder.decode(in, out, false);
156+
if (result.isOverflow()) {
157+
size *= 2;
158+
} else {
159+
buffer.reset();
160+
buffer.write(in.array(), in.arrayOffset(), in.remaining());
161+
break;
162+
}
163+
}
164+
if (out.position() > 0) {
165+
out.flip();
166+
virtual.write(out);
167+
masterInputOutput.write(virtual.read().getBytes());
168+
}
169+
}
170+
}
171+
172+
@Override
173+
public void close() throws IOException {
174+
flush();
175+
}
176+
}
177+
}
178+
179+
public static void main(String[] args) throws InterruptedException, IOException {
180+
181+
try (Terminal terminal = TerminalBuilder.builder().build()) {
182+
Attributes savedAttributes = terminal.enterRawMode();
183+
terminal.puts(enter_ca_mode);
184+
int height = terminal.getHeight();
185+
186+
Display display = new Display(terminal, true);
187+
display.resize(height, terminal.getWidth());
188+
189+
// Build Strings to displayed
190+
List<AttributedString> lines1 = new ArrayList<>();
191+
for (int i = 1; i < height + 1; i++) {
192+
lines1.add(new AttributedString(String.format("%03d: %s", i, "Chaine de test...")));
193+
}
194+
195+
List<AttributedString> lines2 = new ArrayList<>();
196+
for (int i = 0; i < height; i++) {
197+
lines2.add(new AttributedString(String.format("%03d: %s", i, "Chaine de test...")));
198+
}
199+
200+
// Display with tempo
201+
display.update(lines1, 0);
202+
Thread.sleep(3000);
203+
204+
display.update(lines2, 0);
205+
Thread.sleep(3000);
206+
207+
terminal.setAttributes(savedAttributes);
208+
terminal.puts(exit_ca_mode);
209+
}
210+
}
211+
}

‎terminal/src/test/java/org/jline/utils/ScreenTerminal.java

+1,972
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.