Properly support 2J sequence for clearing the screen #5971

Open
opened 2026-01-31 00:26:49 +00:00 by claunia · 0 comments
Owner

Originally created by @zadjii-msft on GitHub (Jan 15, 2020).

This was taken from MSFT:21096414, which was found here in the code:

23d1bcbd94/src/renderer/vt/XtermEngine.cpp (L69-L78)

Apparently this bug never made the hop from ADO to Github. Full text below:

Currently, we only ever send one CSI 2 J from conpty - when we first start up.
Otherwise, the other call to ClearScreen is behind a conditional with !resized - see XtermEngine::StartPaint:

auto dirtyRect = GetDirtyRectInChars();
const auto dirtyView = Viewport::FromInclusive(dirtyRect);
if (!_resized && dirtyView == _lastViewport)
{
    RETURN_IF_FAILED(_ClearScreen());
    _clearedAllThisFrame = true;
}

Unfortunately, !resized will never be true - in state.cpp, in VtEngine::UpdateViewport, we always set _resized to true, and we always call UpdateViewport at the start of as frame.

What that means currently is that the terminal will never get cleared when the conpty is cleared.
Removing the _resized check, or changing the assignment of _resized to only when the dimensions change is NOT correct however.

There's no way to tell the difference between a frame that was all invalid was because the screen was cleared or because it contains all new text.

For example, if you make the simple fix described above, and run help or ping in vtpipeterm, then scroll into history, you might see the output duplicated. This would be because the pty emitted an entire screen of output, then on the next frame, determined everything was invalid, then cleared the terminal screen, then re-emitted the same screen of text.

So we're might be doing something funky in the full screen writing case, where we on the next frame still think everything is invalid. Maybe the scroll delta is causing us to have invalid text up near the top of the pty, causing us to think the screen should be cleared after a full screen of text.

Alternatively, as a half measure, we could have the adapter handle 2J, then in pty mode return false, causing the state machine to write the 2J to the renderer as well. That's probably not the right solution - we might want a specific call for the adapter to tell the renderer that it's requested an EraseAll, so the renderer can update it's state accordingly. Alternatively, the IRenderTarget could have a TriggerClearAll that does specifically that, though that would be relatively unused by the other engines, so I'm not a fan of that.
This half measure isn't great, because it wouldn't work for cmd.exe's cls - it would only work for things emitting a 2J (wsl)

Originally created by @zadjii-msft on GitHub (Jan 15, 2020). This was taken from MSFT:21096414, which was found here in the code: https://github.com/microsoft/terminal/blob/23d1bcbd94bbc8992bb9a30c8d31e3c7878528da/src/renderer/vt/XtermEngine.cpp#L69-L78 Apparently this bug never made the hop from ADO to Github. Full text below: Currently, we only ever send one `CSI 2 J` from conpty - when we first start up. Otherwise, the other call to `ClearScreen` is behind a conditional with `!resized` - see `XtermEngine::StartPaint`: ```c++ auto dirtyRect = GetDirtyRectInChars(); const auto dirtyView = Viewport::FromInclusive(dirtyRect); if (!_resized && dirtyView == _lastViewport) { RETURN_IF_FAILED(_ClearScreen()); _clearedAllThisFrame = true; } ``` Unfortunately, `!resized` will *never* be true - in `state.cpp`, in `VtEngine::UpdateViewport`, we always set `_resized` to true, and we always call `UpdateViewport` at the start of as frame. What that means currently is that the terminal will never get cleared when the conpty is cleared. Removing the `_resized` check, or changing the assignment of `_resized` to only when the dimensions change is NOT correct however. There's no way to tell the difference between a frame that was all invalid was because the screen was cleared or because it contains all new text. For example, if you make the simple fix described above, and run `help` or `ping` in `vtpipeterm`, then scroll into history, you might see the output duplicated. This would be because the pty emitted an entire screen of output, then on the next frame, determined everything was invalid, then cleared the terminal screen, then re-emitted the same screen of text. So we're might be doing something funky in the full screen writing case, where we on the next frame still think everything is invalid. Maybe the scroll delta is causing us to have invalid text up near the top of the pty, causing us to think the screen should be cleared after a full screen of text. Alternatively, as a half measure, we could have the adapter handle 2J, then in pty mode return false, causing the state machine to write the 2J to the renderer as well. That's probably not the right solution - we might want a specific call for the adapter to tell the renderer that it's requested an `EraseAll`, so the renderer can update it's state accordingly. Alternatively, the `IRenderTarget` could have a `TriggerClearAll` that does specifically that, though that would be relatively unused by the other engines, so I'm not a fan of that. This half measure isn't great, because it wouldn't work for cmd.exe's `cls` - it would only work for things emitting a 2J (wsl)
claunia added the Area-RenderingIssue-TaskNeeds-Tag-FixProduct-ConptyPriority-2 labels 2026-01-31 00:26:49 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#5971