-
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
Refactor the SGR implementation in AdaptDispatch #5758
Conversation
…y with SetAttributes.
…tribute constructor.
… dependence on internal details.
…ding GetColorTableEntry API.
i am so excited |
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.
This looks great to me. I want both @zadjii-msft and @miniksa to sign off, but if one of them isn't comfortable doing so alone I'll be the "third". 😄
@@ -518,8 +518,7 @@ OutputCellView OutputCellIterator::s_GenerateView(const CHAR_INFO& charInfo) noe | |||
dbcsAttr.SetTrailing(); | |||
} | |||
|
|||
TextAttribute textAttr; | |||
textAttr.SetFromLegacy(charInfo.Attributes); | |||
const TextAttribute textAttr(charInfo.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.
tiny nit: it took me a while looking at this to get over the "most vexing parse"; can we use {}
universal init syntax?
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.
Yeah I'm a fan of the {}
syntax too, but most of the surrounding code is using ()
initialization, at least in the areas I was modifying. So if we want to change that, I think it's best to handle that in a separate code cleanup PR that replaces everything consistently.
bool HasIdenticalVisualRepresentationForBlankSpace(const TextAttribute& other, const bool inverted = false) const noexcept | ||
{ | ||
// sneaky-sneaky: I'm using xor here | ||
// inverted is whether there's a global invert; Reverse is a local one. | ||
// global ^ local == true : the background attribute is actually the visible foreground, so we care about the foregrounds being identical | ||
// global ^ local == false: the foreground attribute is the visible foreground, so we care about the backgrounds being identical | ||
const auto checkForeground = (inverted != _IsReverseVideo()); | ||
const auto checkForeground = (inverted != IsReverseVideo()); |
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.
Wondering if we should move more of the getters into the header and mark them as constexprable instead of stripping it off. Thoughts?
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 glad you brought this up, because I was actually meaning to propose moving more methods out of the header. I don't know if I'm missing something, but I couldn't see any reason why some methods were defined inline and others weren't - it seemed to me to be entirely arbitrary. And other than maybe the constructors, I couldn't see any benefit in making these methods constexpr
if they're not actually being evaluated as constants anywhere.
That said, I don't feel strongly about it either way, but I would like to understand the reasoning behind the decision. Like what criteria do we use to decide whether a method should be defined inline in the header or not?
{ | ||
RETURN_HR_IF(E_INVALIDARG, index >= 256); | ||
try | ||
{ | ||
Globals& g = ServiceLocator::LocateGlobals(); | ||
CONSOLE_INFORMATION& gci = g.getConsoleInformation(); | ||
|
||
gci.SetColorTableEntry(index, value); | ||
gci.SetColorTableEntry(::Xterm256ToWindowsIndex(index), value); |
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.
tear_of_joy.exe
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.
Would love to get rid of this mapping entirely at some point, but for now it seems preferable to handle here, and keep the dispatch code cleaner.
@@ -102,8 +102,7 @@ class OutputCellIteratorTests | |||
{ | |||
SetVerifyOutput settings(VerifyOutputSettings::LogOnlyFailures); | |||
|
|||
TextAttribute attr; | |||
attr.SetFromLegacy(FOREGROUND_RED | BACKGROUND_BLUE); | |||
const TextAttribute attr(FOREGROUND_RED | BACKGROUND_BLUE); |
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.
(nit: "most vexing parse" comment again)
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.
As I mentioned above, this is just the style of the surrounding code. Notice the settings variable above, and the iterator and view a couple lines below.
// - <none> | ||
void AdaptDispatch::s_ApplyColors(WORD& attr, const WORD applyThis, const bool isForeground) noexcept | ||
{ | ||
#pragma warning(suppress : 26496) // SA is wrong. This variable is assigned more than once. |
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 love that this SA comment is just.. entirely incorrect. It's assigned once. LOL.
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.
That'd be me. I had the arguments that were const vs. not in WI_UpdateFlagsInMask
incorrect in my head. Woops.
// - isForeground - Location to place whether or not the parsed color is for the foreground or not. | ||
// - optionsConsumed - Location to place the number of options we consumed parsing this option. | ||
// - attr - The attribute that will be updated with the parsed color. | ||
// - isForeground - Whether or not the parsed color is for the foreground or not. |
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.
// - isForeground - Whether or not the parsed color is for the foreground or not. | |
// - isForeground - Whether or not the parsed color is for the foreground. |
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 trust github not to screw up the line endings if I accept this here, but I'll make another commit with your suggested comment changes.
optionsConsumed = 2; | ||
const size_t tableIndex = til::at(options, 1); | ||
COLORREF rgbColor; | ||
if (_pConApi->PrivateGetColorTableEntry(tableIndex, rgbColor)) | ||
{ |
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.
{ | |
{ | |
// TODO GH#xxxx: Decouple xterm-256color indexed storage from RGB storage |
(I don't know if this is best covered by 2661.)
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.
Yeah, this can't be done until the color tables are unified (essentially issue #1223). I've got a followup PR in the works that will address this, but I'll add a TODO comment in the meantime.
const BYTE BLUE_ATTR = 0x01; | ||
const BYTE GREEN_ATTR = 0x02; | ||
const BYTE RED_ATTR = 0x04; | ||
const BYTE BRIGHT_ATTR = 0x08; | ||
const BYTE DARK_BLACK = 0; | ||
const BYTE DARK_RED = RED_ATTR; | ||
const BYTE DARK_GREEN = GREEN_ATTR; | ||
const BYTE DARK_YELLOW = RED_ATTR | GREEN_ATTR; | ||
const BYTE DARK_BLUE = BLUE_ATTR; | ||
const BYTE DARK_MAGENTA = RED_ATTR | BLUE_ATTR; | ||
const BYTE DARK_CYAN = GREEN_ATTR | BLUE_ATTR; | ||
const BYTE DARK_WHITE = RED_ATTR | GREEN_ATTR | BLUE_ATTR; | ||
const BYTE BRIGHT_BLACK = BRIGHT_ATTR; | ||
const BYTE BRIGHT_RED = BRIGHT_ATTR | RED_ATTR; | ||
const BYTE BRIGHT_GREEN = BRIGHT_ATTR | GREEN_ATTR; | ||
const BYTE BRIGHT_YELLOW = BRIGHT_ATTR | RED_ATTR | GREEN_ATTR; | ||
const BYTE BRIGHT_BLUE = BRIGHT_ATTR | BLUE_ATTR; | ||
const BYTE BRIGHT_MAGENTA = BRIGHT_ATTR | RED_ATTR | BLUE_ATTR; | ||
const BYTE BRIGHT_CYAN = BRIGHT_ATTR | GREEN_ATTR | BLUE_ATTR; | ||
const BYTE BRIGHT_WHITE = BRIGHT_ATTR | RED_ATTR | GREEN_ATTR | BLUE_ATTR; |
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.
we can probably call these static constexpr
; static so they don't appears as global symbols, and constexpr because it feels most right
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.
Again I'm in agreement with you on this - I prefer constexpr
- but I just copied the format from the TerminalDispatchGraphics
implementation for consistency (see here). And note that both const
and constexpr
have internal linkage in C++ (at least by default), so static
shouldn't be necessary as I understand it.
Also, I'm rather hoping we can rid of these constants in the long term anyway. Or at least unify the two sets into a shared header somewhere. It's just that we've got this weird situation at the moment where the conhost color table is in a different order to the one in Windows Terminal. Ideally we'll get those in sync at some point.
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.
const
andconstexpr
have internal linkage
Fine by me! I'm all for "unify these later and clean them up at the same time". THanks!
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.
We do have src/inc/conattrs.h
, which might be an appropriate place for them 🤔?
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.
Problem is the constants in AdaptDispatch
are currently different from the ones in TerminalDispatch
. Once we can unify them, assuming that's possible, I'd be happy to move them to conattrs. I wanted to leave that for a later PR, though, because I think it might be complicated, and it's not strictly necessary to get to #2661.
None of my comments surfaced above nit level. As always, excellent work. |
@DHowett-MSFT Btw I'm sorry if it seems like I'm rejecting all of your suggestions, but I just wanted to clarify why I took the approach I did in each of those cases. If you (or anyone else) feels strongly about any of these things, do feel free to push me to change them. I won't mind. |
🤔 I'm not sure why I'm suddenly getting the blame for that misspelling. It looks like it's been there since the beginning of time. It's not even a file I've touched. Should I be trying to fix this? |
New misspellings found, please review:
To accept these changes, run the following commands
✏️ Contributor please read this
See the
|
Hey, that's why they're nits. Don't worry about! Your reasoning makes sense, afterall. |
Paging @jsoref - we're seeing the spelling action flag something that already existed and isn't in a whitelist, but can't figure out why it's suddenly complaining. Any ideas? |
This comment has been minimized.
This comment has been minimized.
It used to whitelist -// - Small helper to help mask off the appropriate foreground/background bits in the colors bitfield. So now there are only misspellings for There's a hint about that (admittedly, not stellar) in the to apply bits: remove_obsolete_words=$(mktemp)
echo '#!/usr/bin/perl -ni
my $re=join "|", qw('"
bitfield
...
"');
next if /^($re)(?:$| .*)/;
print;' > $remove_obsolete_words
chmod +x $remove_obsolete_words
for file in .github/actions/spell-check/whitelist/alphabet.txt .github/actions/spell-check/whitelist/web.txt .github/actions/spell-check/whitelist/whitelist.txt; do $remove_obsolete_words $file; done
rm $remove_obsolete_words
(
echo "
Bitfields
href
"
) | sort -u -f | perl -ne 'next unless /./; print' > new_whitelist.txt && mv new_whitelist.txt '.github/actions/spell-check/whitelist/1513de7607d75f067093cee2831770c7940c2a6e.txt' |
I suspect you'll want to move |
Essentially, the goal of the whitelist (soon to be
An example would be a whitelist for Note to self: I need to fix it so that it doesn't call them new misspellings, because, you're right, they aren't new. They're just requests to update the whitelist... I'll need to think about how to do that. Gory implementation detail:
|
I'm not really sure what I need to be doing here, so I could do with some clarification on a few points:
|
For now, let's just go with the easier solution of putting "bitfields" into the dictionary and not trimming the whitelist/fixing |
(Thanks for dealing with this.) |
That file could be Alternatively, You can create a branch w/ that additional commit and push it to your repository w/o pushing it to this PR and let the action run, the action should run (ideally pass) or comment (if it doesn't like things). That way you can avoid churn to the Azure bots attached to the PR. |
Note: |
99f7703
to
9252ea2
Compare
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.
Man this is what I only dreamed this code could be 😄 I really don't have any other complaints or concerns about this. I don't really care one way or another about the constexpr
stuff - I think we in the past had a push to move as much code to constexpr as possible, but we've kinda abandoned that pursuit for now.
const BYTE BLUE_ATTR = 0x01; | ||
const BYTE GREEN_ATTR = 0x02; | ||
const BYTE RED_ATTR = 0x04; | ||
const BYTE BRIGHT_ATTR = 0x08; | ||
const BYTE DARK_BLACK = 0; | ||
const BYTE DARK_RED = RED_ATTR; | ||
const BYTE DARK_GREEN = GREEN_ATTR; | ||
const BYTE DARK_YELLOW = RED_ATTR | GREEN_ATTR; | ||
const BYTE DARK_BLUE = BLUE_ATTR; | ||
const BYTE DARK_MAGENTA = RED_ATTR | BLUE_ATTR; | ||
const BYTE DARK_CYAN = GREEN_ATTR | BLUE_ATTR; | ||
const BYTE DARK_WHITE = RED_ATTR | GREEN_ATTR | BLUE_ATTR; | ||
const BYTE BRIGHT_BLACK = BRIGHT_ATTR; | ||
const BYTE BRIGHT_RED = BRIGHT_ATTR | RED_ATTR; | ||
const BYTE BRIGHT_GREEN = BRIGHT_ATTR | GREEN_ATTR; | ||
const BYTE BRIGHT_YELLOW = BRIGHT_ATTR | RED_ATTR | GREEN_ATTR; | ||
const BYTE BRIGHT_BLUE = BRIGHT_ATTR | BLUE_ATTR; | ||
const BYTE BRIGHT_MAGENTA = BRIGHT_ATTR | RED_ATTR | BLUE_ATTR; | ||
const BYTE BRIGHT_CYAN = BRIGHT_ATTR | GREEN_ATTR | BLUE_ATTR; | ||
const BYTE BRIGHT_WHITE = BRIGHT_ATTR | RED_ATTR | GREEN_ATTR | BLUE_ATTR; |
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.
We do have src/inc/conattrs.h
, which might be an appropriate place for them 🤔?
Hey look @j4james, you're pulling a me when @DHowett-MSFT piles nits all over my PRs! 🤣 |
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.
This is so much better. Thank you very much!
if (boldOn) | ||
{ | ||
attrs.Embolden(); | ||
} | ||
else | ||
{ | ||
attrs.Debolden(); | ||
} |
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.
Mildly sad to see the terminology of "embolden" and "debolden" go. It was always amusing when I came across it.
Argh, we conflicted on the file you didn't want to have to change. I'm sorry about that. :| I'm happy to fix this up if I have maintainer-write access to yourbranch |
Sorry. I'm open to suggestions for how to deal w/ that. It's why I switched to having the script generate random filenames for the whitelist. -- It's also why the cleanup script rewrites the files instead of offering a patch, patches are way too brittle. I suppose the contributor recommendation could be the same: use a SHA, and then at a later point the maintainers could merge the dictionary files together. |
Sorry, I thought I'd already ticked that. Please feel free to fix. |
Hmmm... I'm not sure checking that "allow edits by maintainers" box works. Every time I refresh this page, it's unchecked again. I'll try resolve the conflicts myself. |
Thanks so much for this. |
This is an attempt to simplify the SGR (Select Graphic Rendition) implementation in conhost, to cut down on the number of methods required in the `ConGetSet` interface, and pave the way for future improvements and bug fixes. It already fixes one bug that prevented SGR 0 from being correctly applied when combined with meta attributes. * This a first step towards fixing the conpty narrowing bugs in issue microsoft#2661 * I'm hoping the simplification of `ConGetSet` will also help with microsoft#3849. * Some of the `TextAttribute` refactoring in this PR overlaps with similar work in PR microsoft#1978. ## Detailed Description of the Pull Request / Additional comments The main point of this PR was to simplify the `AdaptDispatch::SetGraphicsRendition` implementation. So instead of having it call a half a dozen methods in the `ConGetSet` API, depending on what kinds of attributes needed to be set, there is now just one call to get current attributes, and another call to set the new value. All adjustments to the attributes are made in the `AdaptDispatch` class, in a simple switch statement. To help with this refactoring, I also made some change to the `TextAttribute` class to make it easier to work with. This included adding a set of methods for setting (and getting) the individual attribute flags, instead of having the calling code being exposed to the internal attribute structures and messing with bit manipulation. I've tried to get rid of any methods that were directly setting legacy, meta, and extended attributes. Other than the fix to the `SGR 0` bug, the `AdaptDispatch` refactoring mostly follows the behaviour of the original code. In particular, it still maps the `SGR 38/48` indexed colors to RGB instead of retaining the index, which is what we ultimately need it to do. Fixing that will first require the color tables to be unified (issue microsoft#1223), which I'm hoping to address in a followup PR. But for now, mapping the indexed colors to RGB values required adding an an additional `ConGetSet` API to lookup the color table entries. In the future that won't be necessary, but the API will still be useful for other color reporting operations that we may want to support. I've made this API, and the existing setter, standardise on index values being in the "Xterm" order, since that'll be essential for unifying the code with the terminal adapter one day. I should also point out one minor change to the `SGR 38/48` behavior, which is that out-of-range RGB colors are now ignored rather than being clamped, since that matches the way Xterm works. ## Validation Steps Performed This refactoring has obviously required corresponding changes to the unit tests, but most were just minor updates to use the new `TextAttribute` methods without any real change in behavior. However, the adapter tests did require significant changes to accommodate the new `ConGetSet` API. The basic structure of the tests remain the same, but the simpler API has meant fewer values needed to be checked in each test case. I think they are all still covering the areas there were intended to, though, and they are all still passing. Other than getting the unit tests to work, I've also done a bunch of manual testing of my own. I've made sure the color tests in Vttest all still work as well as they used to. And I've confirmed that the test case from issue microsoft#5341 is now working correctly. Closes microsoft#5341
🎉 Handy links: |
This is essentially a rewrite of the `TerminalDispatch::SetGraphicsRendition` method, bringing it into closer alignment with the `AdaptDispatch` implementation, simplifying the `ITerminalApi` interface, and making the code easier to extend. It adds support for a number of attributes which weren't previously implemented. REFERENCES * This is a mirror of the `AdaptDispatch` refactoring in PR #5758. * The closer alignment with `AdaptDispatch` is a small step towards solving issue #3849. * The newly supported attributes should help a little with issues #5461 (italics) and #6205 (strike-through). DETAILS I've literally copied and pasted the `SetGraphicsRendition` implementation from `AdaptDispatch` into `TerminalDispatch`, with only few minor changes: * The `SetTextAttribute` and `GetTextAttribute` calls are slightly different in the `TerminalDispatch` version, since they don't return a pointless `success` value, and in the case of the getter, the `TextAttribute` is returned directly instead of by reference. Ultimately I'd like to move the `AdaptDispatch` code towards that way of doing things too, but I'd like to deal with that later as part of a wider refactoring of the `ConGetSet` interface. * The `SetIndexedForeground256` and `SetIndexedBackground256` calls required the color indices to be remapped in the `AdaptDispatch` implementation, because the conhost color table is in a different order to the XTerm standard. `TerminalDispatch` doesn't have that problem, so doesn't require the mapping. * The index color constants used in the 16-color `SetIndexedForeground` and `SetIndexedBackground` calls are also slightly different for the same reason. VALIDATION I cherry-picked this code on top of the #6506 and #6698 PRs, since that's only way to really get the different color formats passed-through to the terminal. I then ran a bunch of manual tests with various color coverage scripts that I have, and confirmed that all the different color formats were being rendered as expected. Closes #6725
🎉 Once again, thanks for the contribution! This pull request was included in a set of conhost changes that was just |
Summary of the Pull Request
This is an attempt to simplify the SGR (Select Graphic Rendition) implementation in conhost, to cut down on the number of methods required in the
ConGetSet
interface, and pave the way for future improvements and bug fixes. It already fixes one bug that prevented SGR 0 from being correctly applied when combined with meta attributes.References
ConGetSet
will also help with De-duplicate code for TerminalDispatch and AdaptDispatch #3849.TextAttribute
refactoring in this PR overlaps with similar work in PR Add support for XTPUSHSGR / XTPOPSGR #1978.PR Checklist
Detailed Description of the Pull Request / Additional comments
The main point of this PR was to simplify the
AdaptDispatch::SetGraphicsRendition
implementation. So instead of having it call a half a dozen methods in theConGetSet
API, depending on what kinds of attributes needed to be set, there is now just one call to get current attributes, and another call to set the new value. All adjustments to the attributes are made in theAdaptDispatch
class, in a simple switch statement.To help with this refactoring, I also made some change to the
TextAttribute
class to make it easier to work with. This included adding a set of methods for setting (and getting) the individual attribute flags, instead of having the calling code being exposed to the internal attribute structures and messing with bit manipulation. I've tried to get rid of any methods that were directly setting legacy, meta, and extended attributes.Other than the fix to the
SGR 0
bug, theAdaptDispatch
refactoring mostly follows the behaviour of the original code. In particular, it still maps theSGR 38/48
indexed colors to RGB instead of retaining the index, which is what we ultimately need it to do. Fixing that will first require the color tables to be unified (issue #1223), which I'm hoping to address in a followup PR.But for now, mapping the indexed colors to RGB values required adding an an additional
ConGetSet
API to lookup the color table entries. In the future that won't be necessary, but the API will still be useful for other color reporting operations that we may want to support. I've made this API, and the existing setter, standardise on index values being in the "Xterm" order, since that'll be essential for unifying the code with the terminal adapter one day.I should also point out one minor change to the
SGR 38/48
behavior, which is that out-of-range RGB colors are now ignored rather than being clamped, since that matches the way Xterm works.Validation Steps Performed
This refactoring has obviously required corresponding changes to the unit tests, but most were just minor updates to use the new
TextAttribute
methods without any real change in behavior. However, the adapter tests did require significant changes to accommodate the newConGetSet
API. The basic structure of the tests remain the same, but the simpler API has meant fewer values needed to be checked in each test case. I think they are all still covering the areas there were intended to, though, and they are all still passing.Other than getting the unit tests to work, I've also done a bunch of manual testing of my own. I've made sure the color tests in Vttest all still work as well as they used to. And I've confirmed that the test case from issue #5341 is now working correctly.