Skip to content
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

Merged
merged 11 commits into from
May 8, 2020

Conversation

j4james
Copy link
Collaborator

@j4james j4james commented May 5, 2020

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

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 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 #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 #5341 is now working correctly.

@ghost ghost added Area-VT Virtual Terminal sequence support Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-2 A description (P2) Product-Conhost For issues in the Console codebase labels May 5, 2020
@DHowett-MSFT
Copy link
Contributor

i am so excited

@j4james j4james marked this pull request as ready for review May 6, 2020 23:31
Copy link
Contributor

@DHowett-MSFT DHowett-MSFT left a 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);
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

Comment on lines +159 to +165
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());
Copy link
Contributor

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?

Copy link
Collaborator Author

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tear_of_joy.exe

Copy link
Collaborator Author

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);
Copy link
Contributor

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)

Copy link
Collaborator Author

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.
Copy link
Contributor

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.

Copy link
Member

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// - isForeground - Whether or not the parsed color is for the foreground or not.
// - isForeground - Whether or not the parsed color is for the foreground.

Copy link
Collaborator Author

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))
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{
{
// TODO GH#xxxx: Decouple xterm-256color indexed storage from RGB storage

(I don't know if this is best covered by 2661.)

Copy link
Collaborator Author

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.

Comment on lines +17 to +36
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;
Copy link
Contributor

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

Copy link
Collaborator Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const and constexpr have internal linkage

Fine by me! I'm all for "unify these later and clean them up at the same time". THanks!

Copy link
Member

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 🤔?

Copy link
Collaborator Author

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.

@DHowett-MSFT
Copy link
Contributor

None of my comments surfaced above nit level. As always, excellent work.

@j4james
Copy link
Collaborator Author

j4james commented May 7, 2020

@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.

@j4james
Copy link
Collaborator Author

j4james commented May 7, 2020

New misspellings found, please review:

  • Bitfields

🤔 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?

@github-actions
Copy link

github-actions bot commented May 7, 2020

New misspellings found, please review:

  • Bitfields
To accept these changes, run the following commands
remove_obsolete_words=$(mktemp)
echo '#!/usr/bin/perl -ni
my $re=join "|", qw('"
bitfield
Emojis
HREF
memcpying
textblock
usr
vpack
"');
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'
✏️ Contributor please read this
  • If the items listed above are names, please add them to .github/actions/spell-check/dictionary/names.txt.
  • If they're APIs, you can add them to a file in .github/actions/spell-check/dictionary/.
  • If they're just things you're using, please add them to an appropriate file in .github/actions/spell-check/whitelist/.
  • If you need to use a specific token in one place and it shouldn't generally be used, you can
    add an item in an appropriate file in .github/actions/spell-check/patterns/.

See the README.md in each directory for more information.

⚠️ Reviewers

At present, the action that triggered this message will not show its ❌ in this PR unless the branch is within this repository.
Thus, you should make sure that this comment has been addressed before encouraging the merge bot to merge this PR.

@DHowett-MSFT
Copy link
Contributor

Btw I'm sorry if it seems like I'm rejecting all of your suggestions

Hey, that's why they're nits. Don't worry about! Your reasoning makes sense, afterall.

@DHowett-MSFT
Copy link
Contributor

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?

@jsoref

This comment has been minimized.

@jsoref
Copy link
Contributor

jsoref commented May 7, 2020

It used to whitelist bitfield, there's logic that groups plurals together. Apparently you removed bitfield:

-// - Small helper to help mask off the appropriate foreground/background bits in the colors bitfield.

So now there are only misspellings for Bitfields.

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'

@jsoref
Copy link
Contributor

jsoref commented May 7, 2020

I suspect you'll want to move bitfield into dictionary/ so that it can come and go, since from your perspective it's a word, not an exception.

@jsoref
Copy link
Contributor

jsoref commented May 7, 2020

Essentially, the goal of the whitelist (soon to be expect -- I need to make a release ...) is allow the fewest non-dictionary items. So if it can suggest a stricter list than the current list, it will. And it does that by removing things which are no longer "misspelled".

  • On the other hand, the dictionary/ is a place where you put things which are just words, things you can use or not use at will.
  • Someone should probably go through the whitelist and determine which should be promoted into the dictionary.

An example would be a whitelist for unknwns and unknwn -- if you had both misspellings of the unknown word family, then initially you whitelist unknwn and it will tolerate both. But if you correct all the instances of unknwn to unknown but leave the instances of unknwns, then it suggests replacing the whitelist entry for unknwn with one for unknwns -- that will allow you to continue using unknwns without introducing new instances of unknwn.


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:

  • The magic it uses starts by grouping things if the singular base is present. (This is partially a compression algorithm, otherwise the whitelist would be much larger, having to contain all the plurals / case variations / ....)
  • What happens here is the singular base is no longer present, can't be grouped to the missing singular base, and thus it's seen as a new word.

@j4james
Copy link
Collaborator Author

j4james commented May 7, 2020

I'm not really sure what I need to be doing here, so I could do with some clarification on a few points:

  1. I've run the script, and it's removed a bunch of words from the whitelist.txt file which I assume are no longer needed. Should I now be committing that updated list?

  2. The script also created a file with a random filename containing the words Bitfields and href. Am I right in assuming those are the words I'm now expected to deal with manually?

  3. Assuming the answer to the second question is yes, what exactly should I do? You say we can add bitfield to the dictionary, and that should fix Bitfields, but what about href? Is that something I need to deal with too? Should I add it back to the whitelist in lowercase (it was previously included as HREF, but the script has now removed that)?

@DHowett-MSFT
Copy link
Contributor

For now, let's just go with the easier solution of putting "bitfields" into the dictionary and not trimming the whitelist/fixing href. We can do that in a later commit so as to not complicate the history of this PR 😄

@DHowett-MSFT
Copy link
Contributor

(Thanks for dealing with this.)

@jsoref
Copy link
Contributor

jsoref commented May 7, 2020

That file could be git added (I need to fix the script to suggest adding it I've written the code for that -- it's in prerelease) -- if there's only one expect file, it just updates it, if there are multiple, it picks a random filename (based on the commit sha) to avoid running into merge conflicts between multiple pending PRs (since as you've seen it will automatically prune from all of them, and thus doesn't really care about their names).

Alternatively, bitfield, update: and bitfields, and href could be added to:
https://github.com/microsoft/terminal/blob/master/.github/actions/spell-check/dictionary/apis.txt

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.

@jsoref
Copy link
Contributor

jsoref commented May 7, 2020

Note: href is probably in the same boat. The repository had HREF (and apparently no unignored instances of href?!)... If you're going to update dictionary, I'd definitely add both. (Sorry, I didn't send the earlier comment earlier. I decided to try to fix the suggestion code...)

Copy link
Member

@zadjii-msft zadjii-msft left a 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.

Comment on lines +17 to +36
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;
Copy link
Member

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 🤔?

@miniksa
Copy link
Member

miniksa commented May 8, 2020

@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.

Hey look @j4james, you're pulling a me when @DHowett-MSFT piles nits all over my PRs! 🤣

Copy link
Member

@miniksa miniksa left a 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!

Comment on lines -71 to -78
if (boldOn)
{
attrs.Embolden();
}
else
{
attrs.Debolden();
}
Copy link
Member

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.

@DHowett-MSFT
Copy link
Contributor

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

@jsoref
Copy link
Contributor

jsoref commented May 8, 2020

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.

@j4james
Copy link
Collaborator Author

j4james commented May 8, 2020

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 thought I'd already ticked that. Please feel free to fix.

@j4james
Copy link
Collaborator Author

j4james commented May 8, 2020

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.

@DHowett-MSFT DHowett-MSFT merged commit e7a2732 into microsoft:master May 8, 2020
@DHowett-MSFT
Copy link
Contributor

Thanks so much for this.

@j4james j4james deleted the refactor-sgr-dispatch branch May 10, 2020 18:38
jelster pushed a commit to jelster/terminal that referenced this pull request May 28, 2020
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
@ghost
Copy link

ghost commented Jun 18, 2020

🎉Windows Terminal Preview v1.1.1671.0 has been released which incorporates this pull request.:tada:

Handy links:

DHowett pushed a commit that referenced this pull request Jul 1, 2020
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
@DHowett
Copy link
Member

DHowett commented Jul 2, 2020

🎉 Once again, thanks for the contribution!

This pull request was included in a set of conhost changes that was just
released with Windows Insider Build 20161.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-VT Virtual Terminal sequence support Issue-Bug It either shouldn't be doing this or needs an investigation. Priority-2 A description (P2) Product-Conhost For issues in the Console codebase
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SGR 0 doesn't work correctly when combined with meta attributes
6 participants