Skip to content

terminal: implement DECSTR soft reset (CSI ! p)#13333

Open
deblasis wants to merge 1 commit into
ghostty-org:mainfrom
deblasis:decstr-soft-reset
Open

terminal: implement DECSTR soft reset (CSI ! p)#13333
deblasis wants to merge 1 commit into
ghostty-org:mainfrom
deblasis:decstr-soft-reset

Conversation

@deblasis

Copy link
Copy Markdown
Contributor

The CSI exclamation point p sequence, known as DECSTR or soft terminal reset, was logged as unimplemented and simply ignored.
Because of that, any settings a program had applied, such as the scroll region, origin mode, SGR attributes, or character set, stayed in effect.
Later output then appeared in the wrong position, with the wrong color, or in the wrong character set until a full reset with RIS or the reset command fixed it.DECSTR resets the terminal's programmable state back to power-up defaults according to DEC STD 070. This includes making the cursor visible, switching to replace mode, using absolute origin, turning autowrap on, applying default SGR, selecting ASCII character sets, setting the full scrolling region, removing protection, and clearing the saved cursor position. Unlike RIS, it leaves the screen contents untouched and does not move the cursor.

Repro

printf '\e[4;9r\e[?6h\e[?7l\e[1;31;44m\e(0\e[!p\e[1;1Hplain-abc\n'

Drop the \e[!p to see today's behavior: the text lands mangled in the
leftover scroll region, DEC line-drawing charset, and SGR.

Before / after

Same reproducer fed through the terminal model. Left: CSI ! p ignored, so
state leaks past the reset. Right: with this change.

ignored (today) soft reset (this PR)

xterm, VTE, and conhost all implement this. Test included.

Note: DEC STD 070 lists autowrap as reset-off; this matches xterm/conhost in
resetting it to the default (on).

@deblasis
deblasis requested a review from a team as a code owner July 14, 2026 18:53
@ghostty-bot ghostty-bot Bot added the vt Control sequence related label Jul 14, 2026

@mitchellh mitchellh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems reasonable to support, obviously not widely used but also low risk to do. I'd be curious to know what other terminals do with some verification (because LLMs will make that shit up).

Comment thread src/terminal/Terminal.zig Outdated
}

/// Soft terminal reset (DECSTR, `CSI ! p`). Unlike `fullReset` (RIS), resets
/// programmable state to power-up defaults without clearing the screen or

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this comment needs some work, e.g. "power-up defaults" is confusing in the context of a non-hardware terminal. I'd be clearer about what the spec says (probably this) and what the practical implementation is for terminal emulators.

@deblasis

deblasis commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Some extra context that I didn't want to land in the body of the commit in case this gets merged:
I bumped into this while experimenting with some conPTY "optimizations" on Windows, for which I have to build a solid framework to test and compare everything down to the cell-by-cell level among the other things.

Re: verification — every claim below is a permalink pinned to a commit SHA, quoting the line it rests on, so none of it has to be taken on faith.

Source Autowrap after DECSTR Clears screen / moves cursor? Evidence
VT510 manual (the spec) OFF "Autowrap (DECAWM): No autowrap."
xterm ON No / No restores autoWrap resource, default True
VTE ON No / No reset(false, false)
conhost / Windows Terminal ON No / No SetSystemMode(AutoWrap, true)
kitty ON No / No .mDECAWM=true — added 2026-07-15
Ghostty (this PR) ON No / No matches all four

Takeaway: the spec resets autowrap OFF, but every emulator that implements DECSTR resets it ON. This PR sides with the implementers. None of them clear the screen or move the cursor, matching us.

xterm documents this deviation deliberately

The clearest statement of spec-vs-practice comes from xterm itself:

Implement soft or hard (full) reset of the VTxxx emulation. There are a couple of differences from real DEC VTxxx terminals (to avoid breaking applications which have come to rely on xterm doing this):

  • autowrap mode should be reset (instead it's reset to the resource default).

So it's a deliberate compatibility choice, not an oversight — which is why I'd rather match it than the letter of the spec.

Spec (VT510)

The DECSTR table lists DECAWM as "No autowrap", alongside DECTCEM "Cursor enabled", IRM "Replace mode", DECOM "Absolute", DECSCA "Normal (erasable by DECSEL and DECSED)", DECSTBM "Top margin = 1; bottom margin = page length", DECSC "Home position". (Citing the VT510 manual specifically; xterm cites DEC STD 070 p. 14-9 for the same sequence.)

xterm — ON

CASE_DECSTRVTReset(xw, False, False)ReallyReset(full=False). The DECSTR branch does bitcpy(&xw->flags, xw->initflags, WRAPAROUND | ...), and initflags is snapshotted from the autoWrap resource, which defaults True. The RIS branch calls CursorSet(screen, 0, 0, ...); the DECSTR branch doesn't, and nothing in the shared path clears or moves the cursor.

VTE — ON (with a caveat worth flagging)

Terminal::DECSTRreset(false, false) = clear_tabstops=false, clear_history=false. In Terminal::reset the block that resets cursor.row/cursor.col and the ring is entirely gated on clear_history, so a soft reset leaves screen and cursor untouched. Modes go through m_modes_private.reset()set_modes(m_default_modes), and DECAWM is in the default-set list:

constexpr Private() : Self{eDEC_AUTOWRAP, eDEC_TEXT_CURSOR, ...}

Caveat, because it looks like a contradiction if you go looking: modes.py says mode_WHAT('DEC_AUTOWRAP', 7, default=False, ...) under a # Default: reset comment. That default= is dead metadata for writable modes — write_modes() only consults it for non-writable MODE_FIXED entries; writable modes emit a bare MODE(name, number). The effective default is the modes.hh constructor above.

conhost / Windows Terminal — ON

AdaptDispatch::SoftReset():

_api.SetSystemMode(ITerminalApi::Mode::AutoWrap, true); // Wrap at end of line.

No clear, no cursor move. It also resets only the active saved-cursor slot, matching xterm's alt-buffer behaviour (GH#19918) — same as this PR.

kitty — ON, as of today

Added in 0816716c (2026-07-15T03:46Z), fixing #10263, filed ~6h earlier. screen_soft_reset saves and restores the cursor around the reset, and the linebuf_clear/historybuf_clear are gated on is_hard_reset — so no clear, no move.

One honest caveat: kitty's soft reset is broader than the others (it also resets tabstops, colours, selections, and the parser), so "kitty matches" holds for autowrap/clear/cursor but they aren't identical implementations. Kovid's own commit message notes the sequence "is largely undefined ... Do what I feel is sensible in these cases."

@deblasis
deblasis requested a review from mitchellh July 15, 2026 12:17
CSI ! p was logged as unimplemented and ignored, so mode, SGR, charset,
and scroll-region state set by a program leaked past a soft reset.

Reset the programmable state to its defaults (cursor visible, replace
mode, absolute origin, autowrap on, default SGR, ASCII charsets, full
scrolling region, unprotected, cleared saved cursor) without touching
the screen contents or the cursor position.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@deblasis
deblasis force-pushed the decstr-soft-reset branch from 33a6b17 to 24efbab Compare July 15, 2026 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vt Control sequence related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants