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/control/impl/ITerminalControlForText.java b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/ITerminalControlForText.java index aca2a5690c0..66e4a82b25b 100644 --- a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/ITerminalControlForText.java +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/control/impl/ITerminalControlForText.java @@ -47,4 +47,19 @@ public interface ITerminalControlForText { */ void enableApplicationCursorKeys(boolean enable); + /** + * A program that asks for bracketed paste (CSI ?2004) wants pasted text marked + * as such, so that it can take it as text rather than as something typed. + */ + default void enableBracketedPaste(boolean enable) { + } + + /** + * Shows or hides the cursor at the program's request (DEC mode 25). + * + * @param show whether the cursor is to be drawn + */ + default void showCursor(boolean show) { + } + } diff --git a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100Emulator.java b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100Emulator.java index 659fd5cd4fd..64b0795a5b8 100644 --- a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100Emulator.java +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100Emulator.java @@ -1264,18 +1264,32 @@ private void processAnsiCommand_X() { } private void processDecPrivateCommand_h() { - int param = getAnsiParameter(0); + // A program may set several modes at once: ncurses turns the mouse on with CSI ? 1006 ; 1000 h. + for (int i = 0; i <= nextAnsiParameter; i++) { + setDecPrivateMode(getAnsiParameter(i)); + } + } + + private void setDecPrivateMode(int param) { switch (param) { case 1: // Enable Application Cursor Keys (DECCKM) terminal.enableApplicationCursorKeys(true); break; + case 25: + // Show cursor (DECTCEM). + terminal.showCursor(true); + break; case 47: case 1047: case 1048: case 1049: // Use Alternate Screen Buffer (ignored). break; + case 2004: + // Bracketed paste: pasted text is wrapped so a program can tell it from typing. + terminal.enableBracketedPaste(true); + break; default: Logger.log("Unsupported command parameter: CSI ?" + param + 'h'); //$NON-NLS-1$ break; @@ -1283,12 +1297,21 @@ private void processDecPrivateCommand_h() { } private void processDecPrivateCommand_l() { - int param = getAnsiParameter(0); + for (int i = 0; i <= nextAnsiParameter; i++) { + resetDecPrivateMode(getAnsiParameter(i)); + } + } + + private void resetDecPrivateMode(int param) { switch (param) { case 1: // Enable Normal Cursor Keys (DECCKM) terminal.enableApplicationCursorKeys(false); break; + case 25: + // Hide cursor (DECTCEM). + terminal.showCursor(false); + break; case 47: case 1047: case 1048: @@ -1296,6 +1319,9 @@ private void processDecPrivateCommand_l() { // Use Normal Screen Buffer (ignored, but reset scroll region). text.setScrollRegion(-1, -1); break; + case 2004: + terminal.enableBracketedPaste(false); + break; default: Logger.log("Unsupported command parameter: CSI ?" + param + 'l'); //$NON-NLS-1$ break; diff --git a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100TerminalControl.java b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100TerminalControl.java index e80d897f11c..e8e2f44d3c8 100644 --- a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100TerminalControl.java +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/emulator/VT100TerminalControl.java @@ -172,6 +172,9 @@ public class VT100TerminalControl implements ITerminalControlForText, ITerminalC */ private final IPropertyChangeListener fPreferenceListener = this::updatePreferences; private final IPropertyChangeListener fFontListener = this::updateFont; + private boolean fBracketedPaste; + private static final String PASTE_START = "\u001b[200~"; //$NON-NLS-1$ + private static final String PASTE_END = "\u001b[201~"; //$NON-NLS-1$ /** * Is protected by synchronize on this @@ -308,7 +311,7 @@ public boolean pasteString(String strText) { if (strText == null) { return false; } - sendString(strText); + sendString(fBracketedPaste ? bracketed(strText) : strText); return true; } @@ -569,6 +572,16 @@ public Shell getShell() { return getCtlText().getShell(); } + /** + * Marks text as pasted, so that a program takes the newlines in it as part of + * the text rather than as the user pressing return on each line. The end marker + * is taken out of the text itself, or the text could close the bracket early and + * the rest of it would arrive as if it had been typed. + */ + private static String bracketed(String text) { + return PASTE_START + text.replace(PASTE_END, "") + PASTE_END; //$NON-NLS-1$ + } + protected void sendChar(char chKey, boolean altKeyPressed) { try { int byteToSend = chKey; @@ -1288,6 +1301,11 @@ public void setState(TerminalState state) { }); } + @Override + public void enableBracketedPaste(boolean enable) { + fBracketedPaste = enable; + } + /** * @param runnable run in display thread */ @@ -1412,6 +1430,13 @@ public String getHoverSelection() { return fCtlText.getHoverSelection(); } + @Override + public void showCursor(boolean show) { + if (fPollingTextCanvasModel != null) { + fPollingTextCanvasModel.setCursorHidden(!show); + } + } + @Override public void updateTerminalDimensions() { getTerminalText().adjustTerminalDimensions(); diff --git a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/textcanvas/AbstractTextCanvasModel.java b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/textcanvas/AbstractTextCanvasModel.java index db57118ddc5..df3b6ca358a 100644 --- a/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/textcanvas/AbstractTextCanvasModel.java +++ b/terminal/bundles/org.eclipse.terminal.control/src/org/eclipse/terminal/internal/textcanvas/AbstractTextCanvasModel.java @@ -33,6 +33,7 @@ abstract public class AbstractTextCanvasModel implements ITextCanvasModel { private boolean fShowCursor; private long fCursorTime; private boolean fCursorIsEnabled; + private boolean fCursorHidden; private final ITerminalTextDataSnapshot fSnapshot; private int fLines; @@ -149,7 +150,12 @@ public int getCursorLine() { @Override public boolean isCursorOn() { - return fShowCursor && fCursorIsEnabled; + return fShowCursor && fCursorIsEnabled && !fCursorHidden; + } + + /** A program hides the cursor while it draws, or for good, with DEC mode 25. */ + public void setCursorHidden(boolean hidden) { + fCursorHidden = hidden; } /** diff --git a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/MockTerminalControlForText.java b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/MockTerminalControlForText.java index 52e8680e2e2..e13e57333c2 100644 --- a/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/MockTerminalControlForText.java +++ b/terminal/tests/org.eclipse.terminal.test/src/org/eclipse/terminal/internal/emulator/MockTerminalControlForText.java @@ -54,6 +54,27 @@ public OutputStream getOutputStream() { } + private boolean cursorShown = true; + private boolean bracketedPaste; + + @Override + public void showCursor(boolean show) { + cursorShown = show; + } + + public boolean isCursorShown() { + return cursorShown; + } + + @Override + public void enableBracketedPaste(boolean enable) { + bracketedPaste = enable; + } + + public boolean isBracketedPaste() { + return bracketedPaste; + } + @Override public void enableApplicationCursorKeys(boolean enable) { throw new UnsupportedOperationException(); 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..3783d3bbf51 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 @@ -11,6 +11,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.Reader; @@ -375,4 +377,17 @@ public void testMalformedStringTerminator() { () -> assertEquals(List.of("TITLE1", "TITLE2"), control.getAllTitles())); } + @Test + public void testDecPrivateModes() { + // the cursor hidden and shown again by a program + run("\u001b[?25l"); + assertFalse(control.isCursorShown()); + run("\u001b[?25h"); + assertTrue(control.isCursorShown()); + // several modes in one sequence, as ncurses sends them: every one of them counts + run("\u001b[?2004;25l"); + assertAll(() -> assertFalse(control.isCursorShown()), () -> assertFalse(control.isBracketedPaste())); + run("\u001b[?25;2004h"); + assertAll(() -> assertTrue(control.isCursorShown()), () -> assertTrue(control.isBracketedPaste())); + } }