Skip to content

Terminal: DEC private mode lists, DECTCEM and bracketed paste - #2901

Merged
akurtakov merged 1 commit into
eclipse-platform:masterfrom
insjang:dec-modes
Nov 6, 2026
Merged

akurtakov merged 1 commit into
eclipse-platform:masterfrom
insjang:dec-modes

Conversation

@insjang

@insjang insjang commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

DEC private mode sequences may carry several modes at once, and ncurses does exactly that when it turns the mouse on: CSI ? 1006 ; 1000 h. The emulator looked at the first parameter only and dropped the rest, so a program could end up with half of what it asked for. Every parameter is now set or reset.

DECTCEM (mode 25) lets a program hide the cursor while it draws, or for good; a full screen program that hides it, such as htop, showed a cursor wandering over its screen. The request is passed to the view, which keeps its own blinking and focus handling and simply does not draw the cursor while a program has hidden it.

Bracketed paste (mode 2004) marks pasted text with CSI 200~ and CSI 201~ so that a program can take it as text rather than as keys typed - a shell does not run every line of a pasted snippet, an editor does not auto-indent it. Programs that ask for it and do not get it misread multi-line pastes. An end marker inside the text is removed so the paste cannot close its own bracket early.

@insjang insjang changed the title Terminal: take every mode in a DEC private set/reset, hide the cursor… Terminal: DEC private mode lists, DECTCEM and bracketed paste Sep 3, 2026
@insjang
insjang force-pushed the dec-modes branch 2 times, most recently from 1767cc4 to f214496 Compare September 30, 2026 08:06
@insjang

insjang commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

This is part of what eclipse-cdt/cdt#737 asks for: the terminal reports TERM=xterm, so shells and prompts (zsh with starship, syntax highlighting, autosuggestions) send xterm sequences that it did not handle. #2900, #2903 and #2904 cover more of the same list.

@insjang

insjang commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on master (with #2894 in), one commit, e8629e9; all terminal tests pass. The flag that hides the cursor is now volatile, since the emulator thread sets it and the paint reads it.

How to test, in a runtime Eclipse with this org.eclipse.terminal.control, Terminal view, local shell:

  1. Several modes in one sequence: run htop or mc and click on the screen.
    Expected: the program reacts to the mouse. ncurses turns it on with CSI ? 1006 ; 1000 h, of which only the first mode used to be taken.
  2. Hidden cursor: printf '\e[?25l'; sleep 3; printf '\e[?25h'
    Expected: no cursor for three seconds, then it is back and blinks as before. In htop no cursor wanders over the screen.
  3. Bracketed paste: in bash 5.1 or later (bracketed paste is on by default), copy two lines such as echo one / echo two and paste them at the prompt.
    Expected: both lines appear at the prompt and nothing runs until Enter. Before this change each line ran as it arrived.
  4. A paste cannot close its own bracket: paste text that contains ESC [ 201 ~ (e.g. copied from printf 'a\e[201~b' output in a file): it arrives as "ab", still as one paste.
  5. Reset on a new connection: run printf '\e[?25l\e[?2004h', disconnect the terminal and connect again.
    Expected: the cursor is shown and pastes are not bracketed until the new shell asks for it.

Unit tests: VT100EmulatorTest covers several modes in one sequence, DECTCEM, bracketed paste with an embedded end marker, and the reset.

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    54 files  ± 0      54 suites  ±0   37m 55s ⏱️ - 1m 39s
 5 001 tests +14   4 979 ✅ +14   22 💤 ±0  0 ❌ ±0 
12 894 runs  +42  12 740 ✅ +42  154 💤 ±0  0 ❌ ±0 

Results for commit 700eadd. ± Comparison against base commit 14b3c15.

♻️ This comment has been updated with latest results.

@insjang
insjang force-pushed the dec-modes branch 3 times, most recently from edb0dab to 85c191e Compare November 5, 2026 06:48
@insjang

insjang commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

DEC private mode sequences may carry several modes at once, and ncurses does exactly that when it turns the mouse on: CSI ? 1006 ; 1000 h. The emulator looked at the first parameter only and dropped the rest, so a program could end up with half of what it asked for. Every parameter is now set or reset.

DECTCEM (mode 25) lets a program hide the cursor while it draws, or for good; a full screen program that hides it, such as htop, showed a cursor wandering over its screen. The request is passed to the view, which keeps its own blinking and focus handling and simply does not draw the cursor while a program has hidden it.

Bracketed paste (mode 2004) marks pasted text with CSI 200~ and CSI 201~ so that a program can take it as text rather than as keys typed - a shell does not run every line of a pasted snippet, an editor does not auto-indent it. Programs that ask for it and do not get it misread multi-line pastes. The text cannot close its own bracket early: every ESC and C1 CSI in it is dropped, as xterm does, so no end marker is left in it or put together from the pieces around one taken out (ESC[20ESC[2011).

A paste sends each line break as a CR, as the Enter key does and as Windows Terminal (microsoft/terminal#3449) and tmux paste. The CR LF of a Windows clipboard reached the shell as two breaks: readline drew two rows for every pasted line, a paste taller than the screen pushed the prompt off it, and the redraw on the next key landed where the prompt no longer was.

Input goes to the process in writes of at most 1000 bytes with a pause between them. A write no longer ends inside a UTF-8 character or an escape sequence: ConPTY has passed the pieces of a sequence split across reads on as plain text (microsoft/terminal#16352), and an end marker cut as ESC[20 and 1~ left the shell taking everything typed after it as part of the paste, Enter included.

How to test, in a Terminal on Windows with WSL bash (bracketed paste is on in bash 5.1 and later):

  1. Copy a few paragraphs of text (more than the terminal's height when doubled) and paste them at the prompt.
  2. Press Backspace a few times, then Up twice: the prompt and the history entry show as usual, with no rows left over from the paste.
  3. Clear the line, press Enter, type: the keys work.
    Before this change the paste was drawn with a blank row after every line, and Up redrew over rows of the paste with the prompt out of sight.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved paste-handling and terminal-state defects can discard line breaks, prevent command execution, and leave cursor visibility or bracketed-paste modes incorrect.

5 open issues
What changed in this PR

This PR extends the Eclipse terminal’s DEC private-mode handling, adding application-controlled cursor visibility and bracketed paste support.

Changes:

  • Process every parameter in DEC private-mode set/reset sequences.
  • Support cursor visibility and bracketed paste, including newline normalization and removal of embedded escape characters.
  • Add boundary-aware input chunking and regression tests.
File Description
terminal/​tests/​org.eclipse.terminal.test/​src/​org/​eclipse/​terminal/​view/​ui/​tests/​InputChunksTest.java Tests character and escape-sequence chunk boundaries.
terminal/​tests/​org.eclipse.terminal.test/​src/​org/​eclipse/​terminal/​test/​AutomatedTestSuite.java Registers the input-chunk tests.
terminal/​tests/​org.eclipse.terminal.test/​src/​org/​eclipse/​terminal/​internal/​emulator/​VT100EmulatorTest.java Tests private modes, connection resets, and paste formatting.
terminal/​tests/​org.eclipse.terminal.test/​src/​org/​eclipse/​terminal/​internal/​emulator/​MockTerminalControlForText.java Records cursor visibility and bracketed-paste state.
terminal/​bundles/​org.eclipse.terminal.view.ui/​src/​org/​eclipse/​terminal/​view/​ui/​streams/​InputStreamMonitor.java Uses boundary-aware input chunking.
terminal/​bundles/​org.eclipse.terminal.view.ui/​src/​org/​eclipse/​terminal/​view/​ui/​internal/​InputChunks.java Adds chunk-boundary detection for UTF-8 and escape sequences.
terminal/​bundles/​org.eclipse.terminal.control/​src/​org/​eclipse/​terminal/​internal/​textcanvas/​AbstractTextCanvasModel.java Suppresses cursor rendering when requested by an application.
terminal/​bundles/​org.eclipse.terminal.control/​src/​org/​eclipse/​terminal/​internal/​emulator/​VT100TerminalControl.java Formats pasted text and forwards cursor visibility changes.
terminal/​bundles/​org.eclipse.terminal.control/​src/​org/​eclipse/​terminal/​internal/​emulator/​VT100Emulator.java Handles mode lists, modes 25 and 2004, and connection resets.
terminal/​bundles/​org.eclipse.terminal.control/​src/​org/​eclipse/​terminal/​internal/​control/​impl/​ITerminalControlForText.java Adds callbacks for bracketed paste and cursor visibility.

Review effort: Balanced

@akurtakov akurtakov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, taking every mode of a DEC private set/reset, DECTCEM and bracketed paste are all welcome, and the input chunking that keeps escape sequences whole is a nice touch. One API concern before this goes in, details inline, and a couple of small things.

  • Three of the Copilot fixes have no test yet: the hidden cursor surviving a tab move (this would fit TerminalControlRecreateUITest), Enter from the input field staying outside the brackets, and local-echo pastes going through unchanged.
  • Nit: with the loop, an empty parameter now counts too, and getAnsiParameter() makes it 1, so CSI ? 1000 ; h also switches application cursor keys on. Skipping empty parameters in the loop would avoid that.

// a stream connector that echoes locally converts line breaks itself
// (InputStreamMonitor), from what it was always given
ITerminalConnector connector = getTerminalConnector();
sendString(connector != null && connector.isLocalEcho() ? strText : pasteText(strText, fBracketedPaste));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pasteString() is API (ITerminalViewControl, "write string to terminal"), and callers use it to run commands, not only to paste: CDT's GdbExtendedInfoThreadsAction does pasteString("info threads\n"). With bash 5.1+ and anything on readline 8.1+, gdb included, bracketed paste is on by default, so such a command now arrives inside the brackets and is only inserted, not run, which is the problem Copilot found for CommandInputFieldWithHistory, but for callers outside this repository that cannot be adjusted. Could pasteString() stay as it was, and the line break conversion and bracketing apply only where the user pastes, paste() and the middle-click paste in TabFolderManager? Then CommandInputFieldWithHistory can stay as it was too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, that is the better split, done in bd1190a. pasteString() is back to what it was and writes its text as it is, and CommandInputFieldWithHistory has no change left. What the user pastes goes through a new default method, ITerminalViewControl.paste(String) (@SInCE 1.2, the package version raised to 1.1.0): Paste and the middle-click paste in TabFolderManager use it, and only there are line breaks sent as Enter and the text bracketed. An empty clipboard sends nothing, and a connector with local echo gets the text unchanged.

Tests: TerminalControlRecreateUITest checks that a cursor the program hid stays hidden after the move, and the new TerminalPasteUITest checks, through a connector that records what it is sent, that paste() converts and brackets, that pasteString("info threads\n") arrives as it is with bracketed paste on, and that a local-echo connector gets the text unchanged. The empty parameter is skipped now, so CSI ? 2004 ; h no longer turns on application cursor keys (testEmptyDecPrivateModeNamesNoMode).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved issues allow oversized DEC mode lists to crash processing and a race during tab moves to leave the cursor visible despite a hide request.

2 open issues
5 resolved since last review

Review effort: Balanced

… on request, and support bracketed paste

DEC private mode sequences may carry several modes at once, and
ncurses does exactly that when it turns the mouse on: CSI ? 1006 ; 1000 h.
The emulator looked at the first parameter only and dropped the rest,
so a program could end up with half of what it asked for. Every
parameter is now set or reset; an empty one names no mode, and the ones
past the 16 that are stored are dropped instead of throwing.

DECTCEM (mode 25) lets a program hide the cursor while it draws, or for
good; a full screen program that hides it, such as htop, showed a
cursor wandering over its screen. The request is passed to the view,
which keeps its own blinking and focus handling and simply does not
draw the cursor while a program has hidden it. Moving the terminal tab
to another view keeps it hidden: the flag is kept on the control and
copied to the new canvas model under one lock with the reader's
writes. The moved canvas of a connected terminal also blinks its cursor
again; it used to start, and stay, with the cursor off. Scroll Lock,
set in the terminal's menu like Inverted colors, stays on after a move.

Bracketed paste (mode 2004) marks pasted text with CSI 200~ and
CSI 201~ so that a program can take it as text rather than as keys
typed - a shell does not run every line of a pasted snippet, an editor
does not auto-indent it. Programs that ask for it and do not get it
misread multi-line pastes. The text cannot close its own bracket early:
every ESC and C1 CSI in it is dropped, as xterm does, so no end marker
is left in it or put together from the pieces around one taken out
(ESC[20ESC[201~1~).

What the user pastes (Paste, and the middle-click paste of the
terminals view, through the new ITerminalViewControl.paste(String)) is
bracketed when the program asked for it, and sends each line break as a
CR, as the Enter key does and as Windows Terminal and tmux paste.
pasteString() still writes its text as it is: callers run commands with
it (CDT sends gdb commands, the command input field its line), and
inside brackets a shell would only insert them. A connector with local
echo gets pasted text unchanged too, since InputStreamMonitor converts
its line breaks itself. The CR LF of a Windows clipboard reached
the shell as two breaks: readline drew two rows for every pasted line, a
paste taller than the screen pushed the prompt off it, and the redraw on
the next key landed where the prompt no longer was.

Input goes to the process in writes of at most 1000 bytes with a pause
between them. A write no longer ends inside a UTF-8 character or an
escape sequence: ConPTY has passed the pieces of a sequence split across
reads on as plain text (microsoft/terminal#16352), and an end marker cut
as ESC[20 and 1~ left the shell taking everything typed after it as part
of the paste, Enter included.

A full reset (ESC c), and the reset before every connection, shows the cursor again and
turns bracketed paste off, so a program that hid the cursor or asked for
bracketed paste and was cut off by a disconnect or a crash does not leave
them set for whatever runs on the next connection.

@akurtakov akurtakov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tested locally:

  • htop no longer shows a cursor wandering over its screen
  • a two-line paste at a bash prompt lands on the command line and runs only on Enter
  • indented block pasted into mcedit arrives unchanged, without the growing indentation.

@akurtakov
akurtakov merged commit 3d40d5d into eclipse-platform:master Nov 6, 2026
17 checks passed
insjang added a commit to insjang/eclipse-modern-terminal that referenced this pull request Nov 7, 2026
…does; pasting to a stream connector with local echo leaves line breaks to it; a hidden cursor stays hidden when the tab moves; erasing into a wide character at the margin clears the wrap mark; drag scrolling follows where the pointer is now. As reviewed for eclipse-platform/eclipse.platform#2901 and #2905.
insjang added a commit to insjang/eclipse-modern-terminal that referenced this pull request Nov 7, 2026
…mmands; what the user pastes (Paste, middle click, dropped files, Run Selection, the input field) goes through the new ITerminalViewControl.paste(String), package io.github.insjang.terminal.control 1.3.0; an empty DEC private mode parameter names no mode; every command but writing text ends the cluster written last; an empty write keeps the layout; copying a whole row keeps what a narrowed screen holds past its margin. As reviewed for eclipse-platform/eclipse.platform#2901 and #2904.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants