diff --git a/terminal/bundles/org.eclipse.terminal.control/META-INF/MANIFEST.MF b/terminal/bundles/org.eclipse.terminal.control/META-INF/MANIFEST.MF index ff5a3fb72be..0c618bbd154 100644 --- a/terminal/bundles/org.eclipse.terminal.control/META-INF/MANIFEST.MF +++ b/terminal/bundles/org.eclipse.terminal.control/META-INF/MANIFEST.MF @@ -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 diff --git a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100EmulatorBackend.java b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100EmulatorBackend.java index 4dfc1f1b560..62379ad64b8 100644 --- a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100EmulatorBackend.java +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100EmulatorBackend.java @@ -165,10 +165,7 @@ public void insertCharacters(int charactersToInsert) { TerminalStyle style = fTerminal.getStyle(line, col - n); fTerminal.setChar(line, col, c, style); } - int last = Math.min(fCursorColumn + n, fColumns); - for (int col = fCursorColumn; col < last; col++) { - fTerminal.setChar(line, col, '\000', null); - } + eraseCells(line, fCursorColumn, Math.min(fCursorColumn + n, fColumns)); } } @@ -177,7 +174,7 @@ public void eraseToEndOfScreen() { synchronized (fTerminal) { eraseLineToEnd(); for (int line = toAbsoluteLine(fCursorLine + 1); line < toAbsoluteLine(fLines); line++) { - fTerminal.cleanLine(line); + eraseLine(line); } } @@ -187,7 +184,7 @@ public void eraseToEndOfScreen() { public void eraseToCursor() { synchronized (fTerminal) { for (int line = toAbsoluteLine(0); line < toAbsoluteLine(fCursorLine); line++) { - fTerminal.cleanLine(line); + eraseLine(line); } eraseLineToCursor(); } @@ -197,7 +194,7 @@ public void eraseToCursor() { public void eraseAll() { synchronized (fTerminal) { for (int line = toAbsoluteLine(0); line < toAbsoluteLine(fLines); line++) { - fTerminal.cleanLine(line); + eraseLine(line); } } } @@ -205,27 +202,45 @@ public void eraseAll() { @Override public void eraseLine() { synchronized (fTerminal) { - fTerminal.cleanLine(toAbsoluteLine(fCursorLine)); + eraseLine(toAbsoluteLine(fCursorLine)); } } + private void eraseLine(int line) { + fTerminal.cleanLine(line); + eraseCells(line, 0, fColumns); + } + @Override public void eraseLineToEnd() { synchronized (fTerminal) { - int line = toAbsoluteLine(fCursorLine); - for (int col = fCursorColumn; col < fColumns; col++) { - fTerminal.setChar(line, col, '\000', null); - } + eraseCells(toAbsoluteLine(fCursorLine), fCursorColumn, fColumns); + } + } + + /** + * Erasing leaves behind the background a program is writing in, not the one the + * terminal started with (xterm's "back color erase", which every terminal that + * supports colour implements). Colouring a run of a line by setting a background + * and erasing to the end of it is how a program paints anything wider than the + * characters it has to put there, and without this such a line keeps the + * terminal's own colour from its last character on. xterm applies this to every + * erase (EL, ED, ECH) and to the cells insertion and deletion uncover (ICH, DCH). + */ + private void eraseCells(int line, int from, int to) { + fWrapPending = false; // erasing, inserting and deleting all forget a pending wrap + // Only the background carries over; what is erased is not bold or underlined. + TerminalStyle plain = getDefaultStyle(); + TerminalStyle erased = plain == null || fStyle == null ? fStyle : plain.setBackground(fStyle); + for (int col = from; col < to; col++) { + fTerminal.setChar(line, col, '\000', erased); } } @Override public void eraseLineToCursor() { synchronized (fTerminal) { - int line = toAbsoluteLine(fCursorLine); - for (int col = 0; col <= fCursorColumn; col++) { - fTerminal.setChar(line, col, '\000', null); - } + eraseCells(toAbsoluteLine(fCursorLine), 0, fCursorColumn + 1); } } @@ -251,10 +266,7 @@ public void deleteCharacters(int n) { TerminalStyle style = fTerminal.getStyle(line, col); fTerminal.setChar(line, col - n, c, style); } - int first = Math.max(fCursorColumn, fColumns - n); - for (int col = first; col < fColumns; col++) { - fTerminal.setChar(line, col, '\000', null); - } + eraseCells(line, Math.max(fCursorColumn, fColumns - n), fColumns); } } @@ -353,6 +365,7 @@ private int doLineWrap() { * MUST be called from a synchronized block! */ private void doNewline() { + fWrapPending = false; // DEC STD 070: any move of the cursor forgets a pending wrap if (fCursorLine == fScrollRegion.getBottomLine()) { scrollUp(1); } else if (fCursorLine + 1 >= fLines) { @@ -374,6 +387,7 @@ public void processNewline() { } private void doReverseLineFeed() { + fWrapPending = false; if (fCursorLine == fScrollRegion.getTopLine()) { scrollDown(1); } else { @@ -436,6 +450,7 @@ public void setCursorLine(int targetLine) { targetLine = fLines - 1; } fCursorLine = targetLine; + fWrapPending = false; // We make the assumption that nobody is changing the // terminal cursor except this class! // This assumption gives a huge performance improvement @@ -505,10 +520,7 @@ public void scrollDown(int n) { public void eraseCharacters(int n) { synchronized (fTerminal) { int line = toAbsoluteLine(fCursorLine); - int end = Math.min(fCursorColumn + n, fColumns); - for (int col = fCursorColumn; col < end; col++) { - fTerminal.setChar(line, col, '\000', null); - } + eraseCells(line, fCursorColumn, Math.min(fCursorColumn + n, fColumns)); } } } diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/VT100EmulatorBackendTest.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/VT100EmulatorBackendTest.java index aecbc640616..42d5e445c35 100644 --- a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/VT100EmulatorBackendTest.java +++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/VT100EmulatorBackendTest.java @@ -17,6 +17,7 @@ package org.eclipse.terminal.internal.emulator; import static org.eclipse.terminal.model.TerminalColor.BLACK; +import static org.eclipse.terminal.model.TerminalColor.GREEN; import static org.eclipse.terminal.model.TerminalColor.WHITE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -1086,4 +1087,81 @@ public void testNewlineInTopAnchoredScrollRegionCurrentlyDiscardsTopLine() { assertNull(term.getChars(3)); assertEquals("4444", new String(term.getChars(4))); // footer below the region is untouched } + + @Test + public void testEraseKeepsBackground() { + ITerminalTextData term = makeITerminalTextData(); + IVT100EmulatorBackend vt100 = makeBakend(term); + term.setMaxHeight(4); + vt100.setDimensions(4, 8); + vt100.setDefaultStyle(TerminalStyle.getStyle(WHITE, BLACK)); + TerminalStyle onGreen = TerminalStyle.getStyle(WHITE, GREEN).setBold(true); + // a program paints a run wider than its text by setting a background and erasing + vt100.setStyle(onGreen); + vt100.setCursor(0, 0); + vt100.appendString("ab"); + vt100.eraseLineToEnd(); + assertEquals(GREEN, term.getStyle(0, 5).getBackgroundTerminalColor()); + assertEquals(GREEN, term.getStyle(0, 7).getBackgroundTerminalColor()); + assertFalse(term.getStyle(0, 5).isBold()); // only the background carries over + // the same for erase to cursor, whole line, ECH, and the cells ICH and DCH uncover + vt100.setCursor(1, 3); + vt100.eraseLineToCursor(); + assertEquals(GREEN, term.getStyle(1, 0).getBackgroundTerminalColor()); + vt100.setCursor(2, 0); + vt100.eraseLine(); + assertEquals(GREEN, term.getStyle(2, 7).getBackgroundTerminalColor()); + vt100.setStyle(TerminalStyle.getStyle(WHITE, BLACK)); + vt100.setCursor(3, 0); + vt100.appendString("0123456"); // one short of the margin, so as not to wrap + vt100.setStyle(onGreen); + vt100.setCursor(3, 1); + vt100.eraseCharacters(2); + assertEquals(GREEN, term.getStyle(3, 2).getBackgroundTerminalColor()); + assertEquals(BLACK, term.getStyle(3, 3).getBackgroundTerminalColor()); + vt100.setCursor(3, 4); + vt100.insertCharacters(1); + assertEquals(GREEN, term.getStyle(3, 4).getBackgroundTerminalColor()); + assertEquals('4', term.getChar(3, 5)); + vt100.deleteCharacters(1); + assertEquals(GREEN, term.getStyle(3, 7).getBackgroundTerminalColor()); + // erasing in the default background stays default + vt100.setStyle(TerminalStyle.getStyle(WHITE, BLACK)); + vt100.setCursor(0, 0); + vt100.eraseToEndOfScreen(); + assertEquals(BLACK, term.getStyle(2, 7).getBackgroundTerminalColor()); + } + + @Test + public void testPendingWrapForgottenByCursorMove() { + ITerminalTextData term = makeITerminalTextData(); + IVT100EmulatorBackend vt100 = makeBakend(term); + term.setMaxHeight(3); + vt100.setDimensions(3, 5); + vt100.setVT100LineWrapping(true); // xterm's deferred wrap, as the process connector sets it + vt100.setCursor(0, 0); + vt100.appendString("abcde"); // fills the row: the wrap is pending, not done + // DEC STD 070: a line feed, a cursor move or an erase forgets the pending wrap + vt100.processNewline(); + vt100.appendString("x"); + assertEquals('x', term.getChar(1, 4)); + assertFalse(term.isWrappedLine(0)); + assertFalse(term.isWrappedLine(1)); + vt100.setCursor(0, 4); + vt100.appendString("e"); + vt100.setCursorLine(2); + vt100.appendString("y"); + assertEquals('y', term.getChar(2, 4)); + vt100.setCursor(0, 4); + vt100.appendString("e"); + vt100.eraseCharacters(1); + vt100.appendString("z"); + assertEquals('z', term.getChar(0, 4)); + assertEquals(0, vt100.getCursorLine()); + // only writing on is a wrap + vt100.setCursor(0, 4); + vt100.appendString("ef"); + assertTrue(term.isWrappedLine(0)); + assertEquals('f', term.getChar(1, 0)); + } }