-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for selective erase operations #14046
Conversation
auto& rowBuffer = textBuffer.GetRowByOffset(row); | ||
const auto& attrs = rowBuffer.GetAttrRow(); | ||
auto& chars = rowBuffer.GetCharRow(); | ||
for (auto col = eraseRect.left; col < eraseRect.right; col++) | ||
{ | ||
// Only unprotected cells are affected. | ||
if (!attrs.GetAttrByColumn(col).IsProtected()) | ||
{ | ||
// The text is cleared but the attributes are left as is. | ||
chars.ClearGlyph(col); | ||
textBuffer.TriggerRedraw(Viewport::FromCoord({ col, row })); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realise this bit will likely have to be rewritten somewhat to work with the new ROW
implementation in PR #13626, so I'm happy to wait for that to merge first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ROW
is going to take much longer than this PR to merge, so I'll ask Leonard to do that part 😄 Thanks though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful and straightforward code. Happy to merge this before ROW
.
Noted: selective erase won't pass over ConPTY today--nor will protection--but due to the way ConPTY renders output it will technically work fine. Interesting!
void TextAttribute::SetDefaultRenditionAttributes() noexcept | ||
{ | ||
_attrs = CharacterAttributes::Normal; | ||
_attrs &= CharacterAttributes::Protected; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we introduce a metaconstant for the rendition attributes? or should we just defer that until we have more non-rendition attributes... :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know. If we did, it would be everything but Protected
, would seems like a pain to maintain. We could go the other way around and create a metaconstant for the "logical" attributes, but that would just be Protected
which feels a bit pointless. I'd say defer it until have more non-rendition attributes, but realistically that'll probably be never.
auto& rowBuffer = textBuffer.GetRowByOffset(row); | ||
const auto& attrs = rowBuffer.GetAttrRow(); | ||
auto& chars = rowBuffer.GetCharRow(); | ||
for (auto col = eraseRect.left; col < eraseRect.right; col++) | ||
{ | ||
// Only unprotected cells are affected. | ||
if (!attrs.GetAttrByColumn(col).IsProtected()) | ||
{ | ||
// The text is cleared but the attributes are left as is. | ||
chars.ClearGlyph(col); | ||
textBuffer.TriggerRedraw(Viewport::FromCoord({ col, row })); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ROW
is going to take much longer than this PR to merge, so I'll ask Leonard to do that part 😄 Thanks though!
switch (opt) | ||
{ | ||
case Default: | ||
attr.SetProtected(false); | ||
break; | ||
case Protected: | ||
attr.SetProtected(true); | ||
break; | ||
case Unprotected: | ||
attr.SetProtected(false); | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I like how clear this is, but it makes me wonder if it shouldn't be attr.SetProtected(opt == Protected)
for some reason. I have no reason to request it specifically other than aesthetics, so f.f. to ignore :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When opt
is something other than those three options it shouldn't have any effect, so that won't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, of course. Thanks!
Also noted: part of making it transit ConPTY (if we were so inclined) would be to make triply sure the (ever-problematic) optimization to avoid sending empty spaces didn't optimize out a protection attribute :) |
The protected attributes don't need to pass over conpty, because all of the logic is happening on the conhost side (edit: I see now you already noted that in the comment above). Although now that I think about it, it's possible the existence of a protected attribute might prevent spaces being optimized where they potentially could have been, but that shouldn't break anything. I have tested this in Windows Terminal, and haven't seen any problems. Edit: Looking at the terminal/src/buffer/out/TextAttribute.hpp Lines 143 to 152 in 1ce22a8
Technically that |
Hello @DHowett! Because this pull request has the Do note that I've been instructed to only help merge pull requests of this repository that have been opened for at least 8 hours, a condition that will be fulfilled in about 4 hours 2 minutes. No worries though, I will be back when the time is right! 😉 p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with this PR. I'm just delaying my approval in case you wish to change anything. Otherwise I'll approve it anyways.
// - <none> | ||
void AdaptDispatch::_SelectiveEraseRect(TextBuffer& textBuffer, const til::rect& eraseRect) | ||
{ | ||
if (eraseRect.left < eraseRect.right && eraseRect.top < eraseRect.bottom) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could just write if (eraseRect)
here. The operator bool
does exactly this, plus it ensures that all coordinates are >= 0, which seems to me like something we might want in this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that's neat. I'll definitely do that.
switch (opt) | ||
{ | ||
case Default: | ||
attr.SetProtected(false); | ||
break; | ||
case Protected: | ||
attr.SetProtected(true); | ||
break; | ||
case Unprotected: | ||
attr.SetProtected(false); | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But what about this?
switch (opt)
{
case Default:
case Unprotected: // <-- deduplicated
attr.SetProtected(false);
break;
case Protected:
attr.SetProtected(true);
break;
default: // <-- explicit (maybe?)
break;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did consider that, but the reason I left the Default
separate from the Unprotected
was because semantically they're actually different. Default
serves the same purpose as SGR 0
does for rendition attributes - it resets all of the logical attributes. For now that just means resetting the protected attribute, because that's all there is, but if we ever added others, those two cases would definitely be different. I thought it better to get the structure right up front rather than trying to over optimise it.
{ | ||
auto& textBuffer = _api.GetTextBuffer(); | ||
auto attr = textBuffer.GetCurrentAttributes(); | ||
for (size_t i = 0; i < options.size(); i++) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of personal curiosity, when looking at the spec at https://vt100.net/docs/vt510-rm/DECSCA.html it looks like DECSCA accepts one single numeric parameter but you iterate over every one (from zero upwards?). Every subsequent parameter will override its predecessor. I can't find that in the linked spec. Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@christianparpart It's documented in DEC STD-070 on page 5-166 (I can get there directly in my copy of the PDF by selecting page 421). The sequence was designed to be extendable with additional attribute types similar to SGR
. So just like you can have \e[1;22m
turning bold on and off again, you can have \e[1;2"q
turning the protected attribute on and off again.
And just like \e[0m
turns off all rendition attributes, \e[0"q
was actually intended to turn off all logical attributes. It's just that in this case that's effectively the same as \e[2q
, because they never got around to defining any other logical attributes (at least as far as I'm aware).
Edit: I should also mention that everyone gets this wrong, so I wouldn't stress too much about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for that clarification and reference.
I'm assuming this is ready to merge. @msftbot merge this in 10 minutes EDIT: huh, looks like the bot isn't working? Merging! |
🎉 Handy links: |
This PR adds support for the selective erase escape sequences:
DECSED
,DECSEL
, andDECSCA
. They provide a way of marking certain areas ofthe screen as "protected", so you can erase the content everywhere else
without affecting those protected areas.
This adds another bit in the
CharacterAttributes
enum to track theprotected status of each cell, and an operation triggered by the
DECSCA
sequence which can toggle that bit in the active attributes.There there are two new erase operations triggered by the
DECSED
andDECSEL
sequences, which work similar to the existingED
(erase indisplay) and
EL
(erase in line) operations, but which only apply tounprotected cells.
I've also updated the
DECRQSS
settings request, so you can query theactive protected attribute status.
Validation Steps Performed
I've manually confirmed that we pass the selective erase tests in Vttest
now, and I've also manually tested some more complicated edge cases and
confirmed that we match the behavior of the hardware VT240 emulator in
MAME.
For unit testing I've extended the existing erase tests to cover
selective erase as an additional option, I've added a test covering the
DECSCA
sequence, and I've extended theDECRQSS
adapter test toconfirm the attribute reporting is working.
Closes #14029