From e4b2e83bd1bf2626b6b6e3892bf8510722b55a67 Mon Sep 17 00:00:00 2001 From: insjang Date: Mon, 28 Sep 2026 19:35:11 +0900 Subject: [PATCH 1/2] Terminal: keep the screen in the history when it is cleared Erase in display (CSI 2 J) now moves what is on the screen into the history before erasing it, as Windows Terminal and VTE do, instead of destroying it. Blank lines at the bottom of the screen are left out, and the cursor does not move. Where the buffer cannot grow, the oldest lines make way; on a buffer capped at the screen height nothing is kept, as before. This matters most on Windows: ConPTY starts every connection with CSI 2 J CSI H, so reconnecting a terminal erased as many lines of the previous session as the screen was high, and a program that clears the screen took the user's last screen of output with it. CSI 3 J still clears the history, so "clear" empties everything as it did. --- .../emulator/VT100EmulatorBackend.java | 25 ++++++++++++++++++ .../emulator/VT100EmulatorBackendTest.java | 26 ++++++++++++++++++- .../internal/emulator/VT100EmulatorTest.java | 15 +++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) 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..8559df5fa7c 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 @@ -196,12 +196,37 @@ public void eraseToCursor() { @Override public void eraseAll() { synchronized (fTerminal) { + // What is on the screen goes into the history before it is erased, as in + // Windows Terminal and VTE, so that clearing the screen does not destroy + // what the user was reading. It matters on Windows, where ConPTY clears + // the screen at the start of every connection. Blank lines at the bottom + // of the screen are left out. Where the buffer is capped at the screen, + // as on an alternate screen, the lines simply go. + int used = fLines; + while (used > 0 && isBlank(toAbsoluteLine(used - 1))) { + used--; + } + for (int i = 0; i < used; i++) { + fTerminal.addLine(); + } for (int line = toAbsoluteLine(0); line < toAbsoluteLine(fLines); line++) { fTerminal.cleanLine(line); } } } + private boolean isBlank(int line) { + char[] chars = fTerminal.getChars(line); + if (chars != null) { + for (char c : chars) { + if (c != '\000' && c != ' ') { + return false; + } + } + } + return true; + } + @Override public void eraseLine() { synchronized (fTerminal) { 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..8a7391962e1 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 @@ -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; @@ -318,7 +319,30 @@ public void testEraseAll() { vt100.setDimensions(3, 4); fill(term, "0000\n" + "1111\n" + "2222\n" + "3333\n" + "4444\n" + "5555"); vt100.eraseAll(); - assertEqualsTerm("0000\n" + "1111\n" + "2222\n" + " \n" + " \n" + " ", toMultiLineText(term)); + // the screen goes into the history; with no room to grow, the oldest lines make way + assertEqualsTerm("3333\n" + "4444\n" + "5555\n" + " \n" + " \n" + " ", toMultiLineText(term)); + } + + @Test + public void testEraseAllKeepsTheScreenInHistory() { + // also on the buffer the terminal actually uses + for (ITerminalTextData term : new ITerminalTextData[] { makeITerminalTextData(), new TerminalTextData() }) { + IVT100EmulatorBackend vt100 = makeBakend(term); + term.setMaxHeight(100); + vt100.setDimensions(4, 4); + fill(term, "0000\n" + "1111\n" + "2222\n" + "3333\n" + "4444\n" + " "); + vt100.setCursor(2, 1); + vt100.eraseAll(); + // the screen was 2222, 3333, 4444 and a blank line, which is not kept + assertEqualsTerm("0000\n" + "1111\n" + "2222\n" + "3333\n" + "4444\n" + " \n" + " \n" + " \n" + " ", + toMultiLineText(term)); + // erasing does not move the cursor + assertEquals(2, vt100.getCursorLine()); + assertEquals(1, vt100.getCursorColumn()); + // a blank screen adds nothing + vt100.eraseAll(); + assertEquals(9, term.getHeight()); + } } @Test diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/VT100EmulatorTest.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/VT100EmulatorTest.java index d7c5692f1a9..b1c9ec596a3 100644 --- a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/VT100EmulatorTest.java +++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/VT100EmulatorTest.java @@ -274,6 +274,21 @@ public void testClear() { assertAll(() -> assertCursorLocation(0, 0), () -> assertTextEquals("")); } + @Test + public void testClearScreenKeepsItInHistory() { + data.setMaxHeight(1000); + for (int i = 0; i < 30; i++) { + run("Hello " + i + "\r\n"); + } + // what ConPTY sends at the start of every connection on Windows + run(CLEAR_ENTIRE_SCREEN + "\u001b[m" + CURSOR_POSITION_TOP_LEFT + "prompt$ "); + int top = data.getHeight() - WINDOW_LINES; // first line of the screen + assertAll(() -> assertEquals("Hello 29", new String(data.getChars(top - 1)).trim()), + () -> assertEquals("Hello 0", new String(data.getChars(0)).trim()), + () -> assertEquals("prompt$", new String(data.getChars(top)).replace('\000', ' ').trim()), + () -> assertCursorLocation(top, 8)); // the top of the screen, counted in the buffer + } + /** * Runs what "up arrow" would send back to terminal in less/man/etc. */ From 0760996285ee133816f548a6f8c9ecff30472fd0 Mon Sep 17 00:00:00 2001 From: insjang Date: Mon, 28 Sep 2026 20:57:00 +0900 Subject: [PATCH 2/2] Terminal: bump org.eclipse.terminal.control to 1.1.300 The change to erasing alters the bundle's content, and the build compares it against the 4.41 release, where 1.1.200 already exists. --- .../bundles/org.eclipse.terminal.control/META-INF/MANIFEST.MF | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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