Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.terminal.control; singleton:=true
Bundle-Version: 1.1.200.qualifier
Bundle-Version: 1.1.300.qualifier
Bundle-Activator: org.eclipse.terminal.internal.control.impl.TerminalPlugin
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ public interface IVT100EmulatorBackend {
*/
void clearAll();

/**
* Switches between the screen a shell writes to, which keeps its scrollback, and
* the one a full screen program is given, which has none. Leaving the alternate
* screen puts back exactly what was on the normal one, so a program that takes
* over the display does not cost the user the history behind it.
*
* @param enable whether to show the alternate screen
*/
void enableAlternateScreen(boolean enable);

/**
* Sets how many lines the normal screen keeps, history included. While the
* alternate screen is showing, the limit is kept for the normal screen and
* applies once it is back, since the alternate screen keeps no history.
*
* @param bufferLineLimit the maximum number of lines, ignored when not positive
*/
void setBufferLineLimit(int bufferLineLimit);

/**
* Sets the Dimensions of the addressable scroll space of the screen....
* Keeps the cursor position relative to the bottom of the screen!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ public void clearAll() {
fBackend.clearAll();
}

@Override
public void enableAlternateScreen(boolean enable) {
fWriter.println("enableAlternateScreen(" + enable + ')'); //$NON-NLS-1$
fBackend.enableAlternateScreen(enable);
}

@Override
public void setBufferLineLimit(int bufferLineLimit) {
fWriter.println("setBufferLineLimit(" + bufferLineLimit + ')'); //$NON-NLS-1$
fBackend.setBufferLineLimit(bufferLineLimit);
}

@Override
public void deleteCharacters(int n) {
fWriter.println("deleteCharacters(" + n + ")"); //$NON-NLS-1$ //$NON-NLS-2$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ public void setInputStreamReader(Reader reader) {
fReader = reader;
}

/**
* @see IVT100EmulatorBackend#setBufferLineLimit(int)
*/
public void setBufferLineLimit(int bufferLineLimit) {
text.setBufferLineLimit(bufferLineLimit);
}

public void setDimensions(int lines, int cols) {
text.setDimensions(lines, cols);
ITerminalConnector telnetConnection = getConnector();
Expand Down Expand Up @@ -1272,9 +1279,12 @@ private void processDecPrivateCommand_h() {
break;
case 47:
case 1047:
case 1048:
case 1049:
// Use Alternate Screen Buffer (ignored).
// Use Alternate Screen Buffer.
text.enableAlternateScreen(true);
break;
case 1048:
// Save cursor position (ignored).
break;
default:
Logger.log("Unsupported command parameter: CSI ?" + param + 'h'); //$NON-NLS-1$
Expand All @@ -1291,10 +1301,13 @@ private void processDecPrivateCommand_l() {
break;
case 47:
case 1047:
case 1048:
case 1049:
// Use Normal Screen Buffer (ignored, but reset scroll region).
text.setScrollRegion(-1, -1);
// Use Normal Screen Buffer, putting back what was on it. Leaving also resets
// the margins, which a repeated request must not do to the normal screen.
text.enableAlternateScreen(false);
break;
case 1048:
// Restore cursor position (ignored).
break;
default:
Logger.log("Unsupported command parameter: CSI ?" + param + 'l'); //$NON-NLS-1$
Expand Down Expand Up @@ -1564,6 +1577,9 @@ public void resetState() {
text.setStyle(text.getDefaultStyle());
text.setScrollRegion(-1, -1);
text.setInsertMode(false);
// a new connection starts on the normal screen, even if the last one ended
// while a full screen program had the alternate one
text.enableAlternateScreen(false);
}

// public OutputStream getOutputStream() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.eclipse.terminal.internal.emulator;

import org.eclipse.terminal.model.ITerminalTextData;
import org.eclipse.terminal.internal.model.TerminalTextDataStore;
import org.eclipse.terminal.model.TerminalStyle;

/**
Expand Down Expand Up @@ -92,13 +93,79 @@ int getHeight() {
int fLines;
int fColumns;
final private ITerminalTextData fTerminal;

/**
* What the normal screen held while the alternate one is showing, and null the
* rest of the time, which is also how we know which screen we are on.
*/
private ITerminalTextData fNormalScreen;
private int fNormalMaxHeight;
/** in the normal buffer, not on the screen: the screen may be resized meanwhile */
private int fNormalCursorLine;
private int fNormalCursorColumn;
private boolean fVT100LineWrapping;
private ScrollRegion fScrollRegion = ScrollRegion.FULL_WINDOW;

public VT100EmulatorBackend(ITerminalTextData terminal) {
fTerminal = terminal;
}

@Override
public void enableAlternateScreen(boolean enable) {
synchronized (fTerminal) {
if (enable == (fNormalScreen != null)) {
Comment on lines +114 to +116

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 381accb: resetState() now leaves the alternate screen, which puts the normal screen and its history back, so the next connection starts there and a late CSI ? 1049 l no longer brings back the old program's screen. Covered by testResetStateLeavesAlternateScreen, on the terminal's real buffer.

// Already on the screen being asked for. Programs do ask twice.
return;
}
if (enable) {
fNormalScreen = new TerminalTextDataStore();
fNormalScreen.copy(fTerminal);
fNormalCursorLine = toAbsoluteLine(fCursorLine);
fNormalCursorColumn = fCursorColumn;
fNormalMaxHeight = fTerminal.getMaxHeight();
// clearAll leaves the buffer the size of the screen, which is what the
// alternate screen is: no history to scroll back through. Capping the
// buffer there keeps it so: scrolling drops the top line instead of
// growing the buffer, as a program on this screen expects.
clearAll();
fTerminal.setMaxHeight(fLines);
Comment thread
insjang marked this conversation as resolved.
Comment on lines +125 to +131

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 381accb: VT100TerminalControl.setBufferLineLimit() now goes through the emulator to a new backend method. While the alternate screen is showing, the backend keeps the limit for the normal screen and applies it on return, so the cap stays and the new limit is not lost. Covered by testBufferLineLimitWhileAlternateScreen.

} else {
fTerminal.copy(fNormalScreen);
fNormalScreen = null;
// the margins were the full screen program's, not the shell's
fScrollRegion = ScrollRegion.FULL_WINDOW;
fTerminal.setMaxHeight(Math.max(fNormalMaxHeight, fTerminal.getHeight()));
// The window may have been resized while the program had the screen, and
// the buffer put back is the one from before. Narrower, and every write
// past its margin throws; shorter than the screen, and the top of the
// screen sits above its first line, so every line number comes out negative.
if (fTerminal.getHeight() < fLines || fTerminal.getWidth() != fColumns) {
fTerminal.setDimensions(Math.max(fTerminal.getHeight(), fLines), fColumns);
}
// back to the line the cursor was on, wherever it is on the screen now
setCursor(fNormalCursorLine - (fTerminal.getHeight() - fLines), fNormalCursorColumn);
}
}
}

@Override
public void setBufferLineLimit(int bufferLineLimit) {
if (bufferLineLimit <= 0) {
return;
}
synchronized (fTerminal) {
if (fNormalScreen != null) {
// the alternate screen keeps its cap; the normal screen gets the limit back
fNormalMaxHeight = bufferLineLimit;
return;
}
if (fTerminal.getHeight() > bufferLineLimit) {
fTerminal.setDimensions(bufferLineLimit, fTerminal.getWidth());
}
fTerminal.setMaxHeight(bufferLineLimit);
}
}

@Override
public void clearAll() {
synchronized (fTerminal) {
Expand All @@ -122,6 +189,22 @@ public void setDimensions(int lines, int cols) {
// relative cursor line
int cl = getCursorLine();
int cc = getCursorColumn();
if (fNormalScreen != null) {
// The alternate screen has no history, so its buffer stays exactly as
// high as the screen. On a shorter screen the lines above the cursor go
// first, as many as it takes to keep the cursor on it; the cap follows
// once the buffer is no higher than it, which the buffer requires.
int drop = Math.max(0, cl + 1 - lines);
if (drop > 0) {
fTerminal.scroll(0, fTerminal.getHeight(), -drop);
}
fLines = lines;
fColumns = cols;
fTerminal.setDimensions(lines, cols);
fTerminal.setMaxHeight(lines);
setCursor(cl - drop, cc);
return;
}
int height = fTerminal.getHeight();
// absolute cursor line
int acl = cl + height - fLines;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1341,15 +1341,8 @@ public int getBufferLineLimit() {

@Override
public void setBufferLineLimit(int bufferLineLimit) {
if (bufferLineLimit <= 0) {
return;
}
synchronized (fTerminalModel) {
if (fTerminalModel.getHeight() > bufferLineLimit) {
fTerminalModel.setDimensions(bufferLineLimit, fTerminalModel.getWidth());
}
fTerminalModel.setMaxHeight(bufferLineLimit);
}
// through the emulator, which knows whether the alternate screen is showing
fTerminalText.setBufferLineLimit(bufferLineLimit);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,16 @@ public void addLine() {

@Override
public void copy(ITerminalTextData source) {
int height = getHeight(), width = getWidth();
fData.copy(source);
fCursorLine = source.getCursorLine();
fCursorColumn = source.getCursorColumn();
// Every line and the size of the buffer with them. Without saying so the view
// keeps drawing what it last saw, which is the screen a program left behind.
sendLinesChangedToSnapshot(0, Math.max(height, getHeight()));
if (height != getHeight() || width != getWidth()) {
sendDimensionsChanged();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.eclipse.terminal.internal.model.TerminalTextData;
import org.eclipse.terminal.internal.model.TerminalTextDataStore;
import org.eclipse.terminal.internal.model.TerminalTextTestHelper;
import org.eclipse.terminal.model.ITerminalTextData;
Expand Down Expand Up @@ -1086,4 +1087,77 @@ public void testNewlineInTopAnchoredScrollRegionCurrentlyDiscardsTopLine() {
assertNull(term.getChars(3));
assertEquals("4444", new String(term.getChars(4))); // footer below the region is untouched
}

@Test
public void testAlternateScreenBuffer() {
ITerminalTextData term = makeITerminalTextData();
IVT100EmulatorBackend vt100 = makeBakend(term);
term.setMaxHeight(100);
vt100.setDimensions(3, 10);
vt100.setCursor(0, 0);
vt100.appendString("one\r\n");
for (int i = 0; i < 5; i++) {
vt100.appendString("more");
vt100.processNewline();
vt100.setCursorColumn(0);
}
vt100.appendString("last");
int historyHeight = term.getHeight();
assertTrue(historyHeight > 3);

vt100.enableAlternateScreen(true);
// no history to scroll back through, and none accumulates: scrolling drops the top line
assertEquals(3, term.getHeight());
assertEquals(3, term.getMaxHeight());
vt100.appendString("alt");
vt100.setCursor(2, 0);
vt100.processNewline();
vt100.processNewline();
assertEquals(3, term.getHeight());
assertNull(term.getChars(0));

// resized while the program had the screen
vt100.setDimensions(5, 7);
vt100.enableAlternateScreen(false);
// the buffer put back is as wide as the screen is now and at least as tall
assertEquals(7, term.getWidth());
assertTrue(term.getHeight() >= 5);
assertEquals(100, term.getMaxHeight());
assertEquals("last", new String(term.getChars(term.getHeight() - 1), 0, 4));
vt100.appendString("x"); // a write past the old margin used to throw
assertEquals(5, vt100.getCursorColumn());
// the cursor is back on the line it left, now the bottom of a taller screen
assertEquals(4, vt100.getCursorLine());
assertEquals("lastx", new String(term.getChars(term.getHeight() - 1), 0, 5));
}

@Test
public void testAlternateScreenShrunkKeepsNoHistory() {
// also on the buffer the terminal actually uses, which rejects a cap below its height
for (ITerminalTextData term : new ITerminalTextData[] { makeITerminalTextData(), new TerminalTextData() }) {
IVT100EmulatorBackend vt100 = makeBakend(term);
term.setMaxHeight(100);
vt100.setDimensions(5, 10);
vt100.enableAlternateScreen(true);
vt100.setCursor(4, 0);
vt100.appendString("bottom");
vt100.setDimensions(3, 10);
assertEquals(3, term.getHeight());
assertEquals(3, term.getMaxHeight());
// the lines above the cursor went, the cursor and its line stayed
assertEquals(2, vt100.getCursorLine());
assertEquals("bottom", new String(term.getChars(2), 0, 6));
for (int i = 0; i < 6; i++) {
vt100.processNewline();
vt100.appendString("x");
}
// scrolling drops the top line instead of growing back to the old height
assertEquals(3, term.getHeight());
vt100.setDimensions(6, 10);
assertEquals(6, term.getHeight());
assertEquals(6, term.getMaxHeight());
vt100.enableAlternateScreen(false);
assertEquals(100, term.getMaxHeight());
}
}
}
Loading
Loading