Feature Request: implement XTPUSHSGR / XTPOPSGR #2514

Closed
opened 2026-01-30 22:57:11 +00:00 by claunia · 9 comments
Owner

Originally created by @jazzdelightsme on GitHub (Jul 3, 2019).

Summary of the new feature/enhancement

I would like to add at least partial support for the control sequences "XTPUSHSGR" and "XTPOPSGR" (as well as their aliases). Summarized from xterm's ctlseqs doc:

XTPUSHSGR

CSI # {
          Push video attributes onto stack (XTPUSHSGR), xterm.  [...]
          If no parameters are given, all of the video attributes are
          saved.  The stack is limited to 10 levels.
CSI # p
          Push video attributes onto stack (XTPUSHSGR), xterm.  This is
          an alias for CSI # { .

XTPOPSGR

CSI # }   Pop video attributes from stack (XTPOPSGR), xterm.  Popping
          restores the video-attributes which were saved using XTPUSHSGR
          to their previous state.
CSI # q   Pop video attributes from stack (XTPOPSGR), xterm.  This is an
          alias for CSI # } .

The problem this helps with is composability of content.

Suppose you have code that emits text that contains SGR control sequences to colorize it. You will find that your text is not easily composable--that is, you may have a format string like "The name is: %s, the position is: %s", and then you want to sprintf some inserts into that. Then that whole string you may want to insert into another, and so forth.

The problem is that to have SGR sequences in that one has to have sort of a global awareness of content and associated coloring for that to work out.

With SGR push/pop, though, you can do things like:

<fgBlack>   : CSI 3 0 m
<fgGreen>   : CSI 3 2 m
<fgYellow>  : CSI 3 3 m
<fgMagenta> : CSI 3 5 m
<fgWhite>   : CSI 3 7 m
<bgBlack>   : CSI 4 0 m
<bgYellow>  : CSI 4 3 m
<bgWhite>   : CSI 4 7 m
<pushSgr>   : CSI # {
<popSgr>    : CSI # }

name = "Dan <pushSgr><fgBlack><bgWhite>Thompson<popSgr>"
position = "<pushSgr><fgMagenta><bgYellow>Developer at Large<popSgr>"
line1 = "<pushSgr><bgBlack><fgWhite>The name is: %s, the position is: %s<popSgr>"

And then you can printf(line1, name, position), and have it all work out.

Without a push/pop mechanism, composing strings like that cannot be done that way at all, unless you do your own custom rendering pass to handle the pushes and pops.

Xterm supports saving/restoring of individual attributes, but this most essential "composability" scenario only requires saving and restoring them all together, and should be simplest to implement.

Proposed technical implementation details (optional)

The "business logic" of these commands is not too complicated... it's just a stack.

Pull Request

PR is here: #1978

Originally created by @jazzdelightsme on GitHub (Jul 3, 2019). # Summary of the new feature/enhancement I would like to add at least partial support for the control sequences "XTPUSHSGR" and "XTPOPSGR" (as well as their aliases). Summarized from xterm's [ctlseqs](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html) doc: ### XTPUSHSGR ``` CSI # { Push video attributes onto stack (XTPUSHSGR), xterm. [...] If no parameters are given, all of the video attributes are saved. The stack is limited to 10 levels. ``` ``` CSI # p Push video attributes onto stack (XTPUSHSGR), xterm. This is an alias for CSI # { . ``` ### XTPOPSGR ``` CSI # } Pop video attributes from stack (XTPOPSGR), xterm. Popping restores the video-attributes which were saved using XTPUSHSGR to their previous state. ``` ``` CSI # q Pop video attributes from stack (XTPOPSGR), xterm. This is an alias for CSI # } . ``` The problem this helps with is composability of content. Suppose you have code that emits text that contains SGR control sequences to colorize it. You will find that your text is not easily composable--that is, you may have a format string like `"The name is: %s, the position is: %s"`, and then you want to `sprintf` some inserts into that. Then that whole string you may want to insert into another, and so forth. The problem is that to have SGR sequences in that one has to have sort of a global awareness of content and associated coloring for that to work out. With SGR push/pop, though, you can do things like: ``` <fgBlack> : CSI 3 0 m <fgGreen> : CSI 3 2 m <fgYellow> : CSI 3 3 m <fgMagenta> : CSI 3 5 m <fgWhite> : CSI 3 7 m <bgBlack> : CSI 4 0 m <bgYellow> : CSI 4 3 m <bgWhite> : CSI 4 7 m <pushSgr> : CSI # { <popSgr> : CSI # } name = "Dan <pushSgr><fgBlack><bgWhite>Thompson<popSgr>" position = "<pushSgr><fgMagenta><bgYellow>Developer at Large<popSgr>" line1 = "<pushSgr><bgBlack><fgWhite>The name is: %s, the position is: %s<popSgr>" ``` And then you can `printf(line1, name, position)`, and have it all work out. Without a push/pop mechanism, composing strings like that cannot be done that way at all, unless you do your own custom rendering pass to handle the pushes and pops. Xterm supports saving/restoring of individual attributes, but this most essential "composability" scenario only requires saving and restoring them all together, and should be simplest to implement. # Proposed technical implementation details (optional) The "business logic" of these commands is not too complicated... it's just a stack. # Pull Request PR is here: #1978
Author
Owner

@zadjii-msft commented on GitHub (Jul 3, 2019):

Woah, I had no idea those existed. Did those get implemented recently?

@zadjii-msft commented on GitHub (Jul 3, 2019): Woah, I had no idea those existed. Did those get implemented recently?
Author
Owner

@jazzdelightsme commented on GitHub (Jul 3, 2019):

One open question: what to do if parameters are supplied to the XTPUSHSGR sequence? I summarized the command in the Issue; here is the full spec:

CSI # {
CSI Ps ; Ps # {
          Push video attributes onto stack (XTPUSHSGR), xterm.  The
          optional parameters correspond to the SGR encoding for video
          attributes, except for colors (which do not have a unique SGR
          code):
            Ps = 1  -> Bold.
            Ps = 2  -> Faint.
            Ps = 3  -> Italicized.
            Ps = 4  -> Underlined.
            Ps = 5  -> Blink.
            Ps = 7  -> Inverse.
            Ps = 8  -> Invisible.
            Ps = 9  -> Crossed-out characters.
            Ps = 1 0  -> Foreground color.
            Ps = 1 1  -> Background color.
            Ps = 2 1  -> Doubly-underlined.

          If no parameters are given, all of the video attributes are
          saved.  The stack is limited to 10 levels.

I don't think it is necessary to start out implementing support for the parameters, but if parameters are seen, what to do? Throw some sort of error? Or just ignore the entire sequence? I think the latter would be best. But note that to properly ignore it one would actually need to do an "empty" push, so that a subsequent XTPOPSGR sequence will have no effect (and not unbalance the stack).

@jazzdelightsme commented on GitHub (Jul 3, 2019): One open question: what to do if parameters are supplied to the XTPUSHSGR sequence? I summarized the command in the Issue; here is the full spec: ``` CSI # { CSI Ps ; Ps # { Push video attributes onto stack (XTPUSHSGR), xterm. The optional parameters correspond to the SGR encoding for video attributes, except for colors (which do not have a unique SGR code): Ps = 1 -> Bold. Ps = 2 -> Faint. Ps = 3 -> Italicized. Ps = 4 -> Underlined. Ps = 5 -> Blink. Ps = 7 -> Inverse. Ps = 8 -> Invisible. Ps = 9 -> Crossed-out characters. Ps = 1 0 -> Foreground color. Ps = 1 1 -> Background color. Ps = 2 1 -> Doubly-underlined. If no parameters are given, all of the video attributes are saved. The stack is limited to 10 levels. ``` I don't think it is necessary to start out implementing support for the parameters, but if parameters *are* seen, what to do? Throw some sort of error? Or just ignore the entire sequence? I think the latter would be best. But note that to properly ignore it one would actually need to do an "empty" push, so that a subsequent XTPOPSGR sequence will have no effect (and not unbalance the stack).
Author
Owner

@jazzdelightsme commented on GitHub (Jul 3, 2019):

@zadjii-msft: yes; first introduced last August in patch #334, with aliases added more recently.

@jazzdelightsme commented on GitHub (Jul 3, 2019): @zadjii-msft: yes; first introduced last August in patch #334, with aliases added more recently.
Author
Owner

@miniksa commented on GitHub (Jul 3, 2019):

This sounds great.

I agree with the interim compromise of doing the empty push and ignoring the excess parameters we can't handle at that moment.

I think it makes sense to get it going in phase 1 then add parameters in phase 2 to make implementation easier. I would just not like to see a long long time go by between the two phases.

I think it's better to not support this at all than leave it in a half-baked state for a long time.

@miniksa commented on GitHub (Jul 3, 2019): This sounds great. I agree with the interim compromise of doing the empty push and ignoring the excess parameters we can't handle at that moment. I think it makes sense to get it going in phase 1 then add parameters in phase 2 to make implementation easier. I would just not like to see a long long time go by between the two phases. I think it's better to not support this at all than leave it in a half-baked state for a long time.
Author
Owner

@j4james commented on GitHub (Dec 10, 2019):

FYI, there's an ongoing discussion regarding these sequences in the xtermjs issue tracker (https://github.com/xtermjs/xterm.js/issues/2570), and whether they're worth implementing as currently specified.

@j4james commented on GitHub (Dec 10, 2019): FYI, there's an ongoing discussion regarding these sequences in the xtermjs issue tracker (https://github.com/xtermjs/xterm.js/issues/2570), and whether they're worth implementing as currently specified.
Author
Owner

@DHowett commented on GitHub (Feb 18, 2021):

Hey @jazzdelightsme, how should/does this interact with RIS hard reset?

@DHowett commented on GitHub (Feb 18, 2021): Hey @jazzdelightsme, how should/does this interact with RIS hard reset?
Author
Owner

@jazzdelightsme commented on GitHub (Feb 18, 2021):

@DHowett That's the beaty of the ring buffer implementation: nothing special needs to be done to "reset to initial state"; you just decide "okay, we're going to call where we are right now 'the beginning'". (More about this in the comment here.)

It's true that if you had done some pushes, and then you executed a RIS, and then you did some pops, it would indeed restore the saved attributes. But I think the point of RIS (and actually I'm not an expert on that; I had to look it up) is that you want to "start over", in which case why would you immediately execute "negative" pops? So I don't think it's necessary to do anything special (like clear out the stack) in response to RIS. Just execute the RIS, and act as if you have 10 fresh, open sgrStack slots ahead of you (because you always do).

@jazzdelightsme commented on GitHub (Feb 18, 2021): @DHowett That's the beaty of the ring buffer implementation: nothing special needs to be done to "reset to initial state"; you just decide "okay, we're going to call where we are right now 'the beginning'". (More about this in the [comment here](https://github.com/microsoft/terminal/blob/eb349935a0c8c6110781605d42c66de8a65c1009/src/types/inc/sgrStack.hpp#L90).) It's true that if you had done some pushes, and then you executed a RIS, and then you did some pops, it would indeed restore the saved attributes. But I think the point of RIS (and actually I'm not an expert on that; I had to look it up) is that you want to "start over", in which case why would you immediately execute "negative" pops? So I don't think it's necessary to do anything special (like clear out the stack) in response to RIS. Just execute the RIS, and act as if you have 10 fresh, open sgrStack slots ahead of you (because you always do).
Author
Owner

@j4james commented on GitHub (Feb 18, 2021):

@jazzdelightsme RIS is essentially the equivalent of a computer reboot. So for something like this I would generally expect RIS to have cleared the stack. However, I think it's probably more important to match whatever Xterm and Mintty are doing - so if they don't clear the stack then it's probably OK that we don't either. If they do clear the stack, we definitely should be matching that behaviour (same goes for a soft reset).

@j4james commented on GitHub (Feb 18, 2021): @jazzdelightsme `RIS` is essentially the equivalent of a computer reboot. So for something like this I would generally expect `RIS` to have cleared the stack. However, I think it's probably more important to match whatever Xterm and Mintty are doing - so if they don't clear the stack then it's probably OK that we don't either. If they do clear the stack, we definitely should be matching that behaviour (same goes for a soft reset).
Author
Owner

@ghost commented on GitHub (Mar 1, 2021):

:tada:This issue was addressed in #1978, which has now been successfully released as Windows Terminal Preview v1.7.572.0.🎉

Handy links:

@ghost commented on GitHub (Mar 1, 2021): :tada:This issue was addressed in #1978, which has now been successfully released as `Windows Terminal Preview v1.7.572.0`.:tada: Handy links: * [Release Notes](https://github.com/microsoft/terminal/releases/tag/v1.7.572.0) * [Store Download](https://www.microsoft.com/store/apps/9n8g5rfz9xk3?cid=storebadge&ocid=badge)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#2514