ConPTY should less aggressively reprint the screen #1235

Closed
opened 2026-01-30 22:19:51 +00:00 by claunia · 7 comments
Owner

Originally created by @Tyriar on GitHub (May 21, 2019).

Tasks in VS Code listens to terminal output to detect diagnostics (errors/warnings) and when watch tasks start and end. It also writes to the terminal itself some status messages such as:

> Executing task: echo %FOO% <

Because of the way ConPTY reprints the screen frequently, this causes problems with this though as it will replace the terminal-side messages like the "Executing task" message and will also report diagnostics multiple times (which I think we de-dupe but could potentially cause issues).

It would be better if ConPTY kept track of when only new output was printed at the cursor and only reprinted when absolutely necessary like on a resize. This would bring it much closer in line to how winpty and nix ptys work.

VS Code issue: https://github.com/microsoft/vscode/issues/74063

Originally created by @Tyriar on GitHub (May 21, 2019). Tasks in VS Code listens to terminal output to detect diagnostics (errors/warnings) and when watch tasks start and end. It also writes to the terminal itself some status messages such as: ``` > Executing task: echo %FOO% < ``` Because of the way ConPTY reprints the screen frequently, this causes problems with this though as it will replace the terminal-side messages like the "Executing task" message and will also report diagnostics multiple times (which I think we de-dupe but could potentially cause issues). It would be better if ConPTY kept track of when only new output was printed at the cursor and only reprinted when absolutely necessary like on a resize. This would bring it much closer in line to how winpty and nix ptys work. VS Code issue: https://github.com/microsoft/vscode/issues/74063
claunia added the Needs-TriageArea-RenderingIssue-BugProduct-Conpty labels 2026-01-30 22:19:51 +00:00
Author
Owner

@Tyriar commented on GitHub (May 21, 2019):

This should also improve performance as it would avoid clearing out all the rows which happens a lot right now.

@Tyriar commented on GitHub (May 21, 2019): This should also improve performance as it would avoid clearing out all the rows which happens a lot right now.
Author
Owner

@zadjii-msft commented on GitHub (May 21, 2019):

Do we have a particular repro case where the screen is repainted when we don't expect it to be?

For the record, conpty already only repaints the regions it believes have changed in the course of a frame. Most of the code is in this directory - with invalidate.cpp and paint.cpp probably being the most interesting. The Renderer in the sibling directory base acts as the controller for painting only the regions the VtEngine (which powers conpty) thinks are invalid.

There was some bug in either RS5 or in-between RS5 and 19H1 where we'd incorrectly repaint the screen whenever the active attributes were changed (which is pretty constantly). I believe that's fixed now, so this shouldn't be as bad as before, but it could always be better. Concrete scenarios would certainly make this easier to drill into.

In the linked scenario, it almost seems like the task is spawning a new conpty instance to run the task, and then writing that output to the parent, is that correct? If that's the case then maybe the child task needs to inherit the cursor position of the parent terminal (using dwFlags= PSEUDOCONSOLE_INHERIT_CURSOR

@zadjii-msft commented on GitHub (May 21, 2019): Do we have a particular repro case where the screen is repainted when we don't expect it to be? For the record, conpty already only repaints the regions it believes have changed in the course of a frame. Most of the code is in [this directory](https://github.com/microsoft/terminal/tree/master/src/renderer/vt) - with invalidate.cpp and paint.cpp probably being the most interesting. The `Renderer` in the sibling directory `base` acts as the controller for painting only the regions the VtEngine (which powers conpty) thinks are invalid. There was some bug in either RS5 or in-between RS5 and 19H1 where we'd incorrectly repaint the screen whenever the active attributes were changed (which is pretty constantly). I believe that's fixed now, so this shouldn't be as bad as before, but it could always be better. Concrete scenarios would certainly make this easier to drill into. In the linked scenario, it almost seems like the task is spawning a _new_ conpty instance to run the task, and then writing that output to the parent, is that correct? If that's the case then maybe the child task needs to inherit the cursor position of the parent terminal (using `dwFlags= PSEUDOCONSOLE_INHERIT_CURSOR`
Author
Owner

@Tyriar commented on GitHub (May 21, 2019):

@zadjii-msft the flow in https://github.com/microsoft/vscode/issues/74063 is roughly:

  1. Instantiate xterm.js
  2. Write "> Executing task: echo %FOO% <" to xterm.js
  3. Launch the process via conpty and hook it up to xterm.js
  4. Conpty reprints the screen right after process launch

@alexr00 what's your Windows 10 build number?

@Tyriar commented on GitHub (May 21, 2019): @zadjii-msft the flow in https://github.com/microsoft/vscode/issues/74063 is roughly: 1. Instantiate xterm.js 2. Write "> Executing task: echo %FOO% <" to xterm.js 3. Launch the process via conpty and hook it up to xterm.js 4. Conpty reprints the screen right after process launch @alexr00 what's your Windows 10 build number?
Author
Owner

@zadjii-msft commented on GitHub (May 21, 2019):

Yea that sounds exactly like the kind of situation where INHERIT_CURSOR is needed.

Since conpty has to maintain the state of a console buffer, by default it assumes that the buffer is empty. However, with INHERIT_CURSOR, conpty asks the terminal on startup where the cursor currently is. When the terminal replies, conpty uses that as the initial cursor position, and won't render any text above that point. It'll use that y-coordinate as a "virtual top" which is inaccessible. As the text scrolls, we'll move that virtualtop up, until the entire buffer is addressable again.

I believe WSL and ssh.exe both use this flag, to make console sessions more continuous. Otherwise, as you've noted, we'll start by clearing the screen, which is only really desirable if you know you're starting from a fresh terminal window.

@zadjii-msft commented on GitHub (May 21, 2019): Yea that sounds exactly like the kind of situation where `INHERIT_CURSOR` is needed. Since conpty has to maintain the state of a console buffer, by default it assumes that the buffer is empty. However, with `INHERIT_CURSOR`, conpty asks the terminal on startup where the cursor currently is. When the terminal replies, conpty uses that as the initial cursor position, and won't render any text above that point. It'll use that y-coordinate as a "virtual top" which is inaccessible. As the text scrolls, we'll move that virtualtop up, until the entire buffer is addressable again. I believe WSL and ssh.exe both use this flag, to make console sessions more continuous. Otherwise, as you've noted, we'll start by clearing the screen, which is only really desirable if you know you're starting from a fresh terminal window.
Author
Owner

@Tyriar commented on GitHub (May 22, 2019):

What's the problem you're trying to fix by not assuming a cursor position of 1,1 when conpty starts?

@Tyriar commented on GitHub (May 22, 2019): What's the problem you're trying to fix by not assuming a cursor position of 1,1 when conpty starts?
Author
Owner

@DHowett-MSFT commented on GitHub (May 22, 2019):

We are assuming a cursor position of 1,1 when ConPTY starts. PSEUDOCONSOLE_INHERIT_CURSOR overrides that behaviour.

The reason we by default assume a cursor position of 1,1 is that Win32 console applications are more likely than Unix VT applications to try to read back the screen buffer contents. They can figure out where the cursor is without asking for a cursor report, and they can request that regions of console screen buffer memory be copied back into their processes. They can also write from their own buffers directly into the console buffer at an absolute (not viewport-relative like VT!) location.

To support these APIs from a pseudoconsole, we had to do two things.

First and foremost, we can't lie to a Win32 console application about where the cursor is, or where any output has been drawn. That would force us to pile up hacks to adjust the location of every buffer- or cursor position-related call. So, we assume the cursor is at 1,1 unless we have explicit knowledge from the hosting terminal as to where the cursor should be.

Because there is no "API" for requesting the cursor position apart from requesting DSR and waiting for a response, INHERIT_CURSOR requires that the connecting terminal provide DSR before it'll continue. Again, this is required for API consistency.

The second thing we do is to ensure that the win32 console and its VT representation are in sync (that is: a console application reading a buffer out of the console won't see blank space where the terminal says there's text, and vice-versa): we clear the screen. This only applies when we home the cursor to 1,1 on startup.

TL;DR: We home the cursor to 1,1 and clear the screen for the widest compatibility with Win32 console applications, and both of those behaviors are overridable because using INHERIT_CURSOR disables the screen clear and the cursor homing.

Double TL;DR: The premise for your question didn't make sense, so I decided it would be best to just explain everything.

@DHowett-MSFT commented on GitHub (May 22, 2019): We _are_ assuming a cursor position of `1,1` when ConPTY starts. `PSEUDOCONSOLE_INHERIT_CURSOR` overrides that behaviour. The reason we _by default_ assume a cursor position of `1,1` is that Win32 console applications are more likely than Unix VT applications to _try to read back the screen buffer contents._ They can figure out where the cursor is without asking for a cursor report, and they can request that regions of console screen buffer memory be copied back into their processes. They can also write from their own buffers directly into the console buffer at an absolute (not viewport-relative like VT!) location. To support these APIs from a pseudoconsole, we had to do two things. First and foremost, we can't lie to a Win32 console application about where the cursor is, or where any output has been drawn. That would force us to pile up hacks to adjust the location of _every buffer- or cursor position-related call_. So, we assume the cursor is at `1,1` unless we have explicit knowledge from the hosting terminal as to where the cursor _should_ be. Because there is no "API" for requesting the cursor position apart from requesting `DSR` and waiting for a response, `INHERIT_CURSOR` requires that the connecting terminal provide `DSR` before it'll continue. Again, this is required for API consistency. The second thing we do is to ensure that the win32 console and its VT representation are in sync (that is: a console application reading a buffer out of the console won't see blank space where the terminal says there's text, and vice-versa): we clear the screen. This only applies when we home the cursor to `1,1` on startup. TL;DR: We home the cursor to `1,1` and clear the screen for the widest compatibility with Win32 console applications, and both of those behaviors are overridable because using `INHERIT_CURSOR` disables the screen clear _and_ the cursor homing. Double TL;DR: The premise for your question didn't make sense, so I decided it would be best to just explain everything.
Author
Owner

@Tyriar commented on GitHub (May 22, 2019):

Thanks it sounds like we should just default to PSEUDOCONSOLE_INHERIT_CURSOR and the repainting issue sounds like a bug that's already fixed.

@Tyriar commented on GitHub (May 22, 2019): Thanks it sounds like we should just default to `PSEUDOCONSOLE_INHERIT_CURSOR` and the repainting issue sounds like a bug that's already fixed.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#1235