mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-09 15:51:05 +00:00
## Summary of the Pull Request This PR adds support for the `SGR 2` escape sequence, which enables the ANSI _faint_ graphic rendition attribute. When a character is output with this attribute set, it uses a dimmer version of the active foreground color. ## PR Checklist * [x] Closes #6703 * [x] CLA signed. * [x] Tests added/passed * [ ] Documentation updated. * [ ] Schema updated. * [x] I've discussed this with core contributors already. Issue number where discussion took place: #6703 ## Detailed Description of the Pull Request / Additional comments There was already an `ExtendedAttributes::Faint` flag in the `TextAttribute` class, but I needed to add `SetFaint` and `IsFaint` methods to access that flag, and update the `SetGraphicsRendition` methods of the two dispatchers to set the attribute on receipt of the `SGR 2` sequence. I also had to update the existing `SGR 22` handler to reset _Faint_ in addition to _Bold_, since they share the same reset sequence. For that reason, I thought it a good idea to change the name of the `SGR 22` enum to `NotBoldOrFaint`. For the purpose of rendering, I've updated the `TextAttribute::CalculateRgbColors` method to return a dimmer version of the foreground color when the _Faint_ attribute is set. This is simply achieved by dividing each color component by two, which produces a reasonable effect without being too complicated. Note that the _Faint_ effect is applied before _Reverse Video_, so if the output it reversed, it's the background that will be faint. The only other complication was the update of the `Xterm256Engine` in the VT renderer. As mentioned above, _Bold_ and _Faint_ share the same reset sequence, so to forward that state over conpty we have to go through a slightly more complicated process than with other attributes. We first check whether either attribute needs to be turned off to send the reset sequence, and then check if the individual attributes need to be turned on again. ## Validation I've extended the existing SGR unit tests to cover the new attribute in the `AdapterTest`, the `ScreenBufferTests`, and the `VtRendererTest`, and added a test to confirm the color calculations when _Faint_ is set in the `TextAttributeTests`. I've also done a bunch of manual testing with all the different VT color types and confirmed that our output is comparable to most other terminals.
231 lines
9.2 KiB
C++
231 lines
9.2 KiB
C++
/*++
|
|
Copyright (c) Microsoft Corporation
|
|
Licensed under the MIT license.
|
|
|
|
Module Name:
|
|
- TextAttribute.hpp
|
|
|
|
Abstract:
|
|
- contains data structure for run-length-encoding of text attribute data
|
|
|
|
Author(s):
|
|
- Michael Niksa (miniksa) 10-Apr-2014
|
|
- Paul Campbell (paulcam) 10-Apr-2014
|
|
|
|
Revision History:
|
|
- From components of output.h/.c
|
|
by Therese Stowell (ThereseS) 1990-1991
|
|
- Pulled into its own file from textBuffer.hpp/cpp (AustDi, 2017)
|
|
- Pulled each of the fg/bg colors into their own abstraction (migrie, Nov 2018)
|
|
--*/
|
|
|
|
#pragma once
|
|
#include "TextColor.h"
|
|
#include "../../inc/conattrs.hpp"
|
|
|
|
#ifdef UNIT_TESTING
|
|
#include "WexTestClass.h"
|
|
#endif
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
class TextAttribute final
|
|
{
|
|
public:
|
|
constexpr TextAttribute() noexcept :
|
|
_wAttrLegacy{ 0 },
|
|
_foreground{},
|
|
_background{},
|
|
_extendedAttrs{ ExtendedAttributes::Normal }
|
|
{
|
|
}
|
|
|
|
explicit constexpr TextAttribute(const WORD wLegacyAttr) noexcept :
|
|
_wAttrLegacy{ gsl::narrow_cast<WORD>(wLegacyAttr & META_ATTRS) },
|
|
_foreground{ s_LegacyIndexOrDefault(wLegacyAttr & FG_ATTRS, s_legacyDefaultForeground) },
|
|
_background{ s_LegacyIndexOrDefault((wLegacyAttr & BG_ATTRS) >> 4, s_legacyDefaultBackground) },
|
|
_extendedAttrs{ ExtendedAttributes::Normal }
|
|
{
|
|
// If we're given lead/trailing byte information with the legacy color, strip it.
|
|
WI_ClearAllFlags(_wAttrLegacy, COMMON_LVB_SBCSDBCS);
|
|
}
|
|
|
|
constexpr TextAttribute(const COLORREF rgbForeground,
|
|
const COLORREF rgbBackground) noexcept :
|
|
_wAttrLegacy{ 0 },
|
|
_foreground{ rgbForeground },
|
|
_background{ rgbBackground },
|
|
_extendedAttrs{ ExtendedAttributes::Normal }
|
|
{
|
|
}
|
|
|
|
static void SetLegacyDefaultAttributes(const WORD defaultAttributes) noexcept;
|
|
static TextAttribute StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept;
|
|
WORD GetLegacyAttributes() const noexcept;
|
|
|
|
std::pair<COLORREF, COLORREF> CalculateRgbColors(const std::basic_string_view<COLORREF> colorTable,
|
|
const COLORREF defaultFgColor,
|
|
const COLORREF defaultBgColor,
|
|
const bool reverseScreenMode = false) const noexcept;
|
|
|
|
bool IsLeadingByte() const noexcept;
|
|
bool IsTrailingByte() const noexcept;
|
|
bool IsTopHorizontalDisplayed() const noexcept;
|
|
bool IsBottomHorizontalDisplayed() const noexcept;
|
|
bool IsLeftVerticalDisplayed() const noexcept;
|
|
bool IsRightVerticalDisplayed() const noexcept;
|
|
|
|
void SetLeftVerticalDisplayed(const bool isDisplayed) noexcept;
|
|
void SetRightVerticalDisplayed(const bool isDisplayed) noexcept;
|
|
|
|
void Invert() noexcept;
|
|
|
|
friend constexpr bool operator==(const TextAttribute& a, const TextAttribute& b) noexcept;
|
|
friend constexpr bool operator!=(const TextAttribute& a, const TextAttribute& b) noexcept;
|
|
friend constexpr bool operator==(const TextAttribute& attr, const WORD& legacyAttr) noexcept;
|
|
friend constexpr bool operator!=(const TextAttribute& attr, const WORD& legacyAttr) noexcept;
|
|
friend constexpr bool operator==(const WORD& legacyAttr, const TextAttribute& attr) noexcept;
|
|
friend constexpr bool operator!=(const WORD& legacyAttr, const TextAttribute& attr) noexcept;
|
|
|
|
bool IsLegacy() const noexcept;
|
|
bool IsBold() const noexcept;
|
|
bool IsFaint() const noexcept;
|
|
bool IsItalic() const noexcept;
|
|
bool IsBlinking() const noexcept;
|
|
bool IsInvisible() const noexcept;
|
|
bool IsCrossedOut() const noexcept;
|
|
bool IsUnderlined() const noexcept;
|
|
bool IsOverlined() const noexcept;
|
|
bool IsReverseVideo() const noexcept;
|
|
|
|
void SetBold(bool isBold) noexcept;
|
|
void SetFaint(bool isFaint) noexcept;
|
|
void SetItalics(bool isItalic) noexcept;
|
|
void SetBlinking(bool isBlinking) noexcept;
|
|
void SetInvisible(bool isInvisible) noexcept;
|
|
void SetCrossedOut(bool isCrossedOut) noexcept;
|
|
void SetUnderline(bool isUnderlined) noexcept;
|
|
void SetOverline(bool isOverlined) noexcept;
|
|
void SetReverseVideo(bool isReversed) noexcept;
|
|
|
|
ExtendedAttributes GetExtendedAttributes() const noexcept;
|
|
|
|
TextColor GetForeground() const noexcept;
|
|
TextColor GetBackground() const noexcept;
|
|
void SetForeground(const TextColor foreground) noexcept;
|
|
void SetBackground(const TextColor background) noexcept;
|
|
void SetForeground(const COLORREF rgbForeground) noexcept;
|
|
void SetBackground(const COLORREF rgbBackground) noexcept;
|
|
void SetIndexedForeground(const BYTE fgIndex) noexcept;
|
|
void SetIndexedBackground(const BYTE bgIndex) noexcept;
|
|
void SetIndexedForeground256(const BYTE fgIndex) noexcept;
|
|
void SetIndexedBackground256(const BYTE bgIndex) noexcept;
|
|
void SetColor(const COLORREF rgbColor, const bool fIsForeground) noexcept;
|
|
|
|
void SetDefaultForeground() noexcept;
|
|
void SetDefaultBackground() noexcept;
|
|
|
|
bool BackgroundIsDefault() const noexcept;
|
|
|
|
void SetStandardErase() noexcept;
|
|
|
|
// This returns whether this attribute, if printed directly next to another attribute, for the space
|
|
// character, would look identical to the other one.
|
|
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());
|
|
return !IsAnyGridLineEnabled() && // grid lines have a visual representation
|
|
// crossed out, doubly and singly underlined have a visual representation
|
|
WI_AreAllFlagsClear(_extendedAttrs, ExtendedAttributes::CrossedOut | ExtendedAttributes::DoublyUnderlined | ExtendedAttributes::Underlined) &&
|
|
// all other attributes do not have a visual representation
|
|
(_wAttrLegacy & META_ATTRS) == (other._wAttrLegacy & META_ATTRS) &&
|
|
((checkForeground && _foreground == other._foreground) ||
|
|
(!checkForeground && _background == other._background)) &&
|
|
_extendedAttrs == other._extendedAttrs;
|
|
}
|
|
|
|
constexpr bool IsAnyGridLineEnabled() const noexcept
|
|
{
|
|
return WI_IsAnyFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE);
|
|
}
|
|
|
|
private:
|
|
static constexpr TextColor s_LegacyIndexOrDefault(const BYTE requestedIndex, const BYTE defaultIndex)
|
|
{
|
|
return requestedIndex == defaultIndex ? TextColor{} : TextColor{ requestedIndex, true };
|
|
}
|
|
|
|
static BYTE s_legacyDefaultForeground;
|
|
static BYTE s_legacyDefaultBackground;
|
|
|
|
WORD _wAttrLegacy;
|
|
TextColor _foreground;
|
|
TextColor _background;
|
|
ExtendedAttributes _extendedAttrs;
|
|
|
|
#ifdef UNIT_TESTING
|
|
friend class TextBufferTests;
|
|
friend class TextAttributeTests;
|
|
template<typename TextAttribute>
|
|
friend class WEX::TestExecution::VerifyOutputTraits;
|
|
#endif
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
// 2 for _wAttrLegacy
|
|
// 4 for _foreground
|
|
// 4 for _background
|
|
// 1 for _extendedAttrs
|
|
static_assert(sizeof(TextAttribute) <= 11 * sizeof(BYTE), "We should only need 11B for an entire TextColor. Any more than that is just waste");
|
|
|
|
enum class TextAttributeBehavior
|
|
{
|
|
Stored, // use contained text attribute
|
|
Current, // use text attribute of cell being written to
|
|
StoredOnly, // only use the contained text attribute and skip the insertion of anything else
|
|
};
|
|
|
|
constexpr bool operator==(const TextAttribute& a, const TextAttribute& b) noexcept
|
|
{
|
|
return a._wAttrLegacy == b._wAttrLegacy &&
|
|
a._foreground == b._foreground &&
|
|
a._background == b._background &&
|
|
a._extendedAttrs == b._extendedAttrs;
|
|
}
|
|
|
|
constexpr bool operator!=(const TextAttribute& a, const TextAttribute& b) noexcept
|
|
{
|
|
return !(a == b);
|
|
}
|
|
|
|
#ifdef UNIT_TESTING
|
|
|
|
#define LOG_ATTR(attr) (Log::Comment(NoThrowString().Format( \
|
|
L#attr L"=%s", VerifyOutputTraits<TextAttribute>::ToString(attr).GetBuffer())))
|
|
|
|
namespace WEX
|
|
{
|
|
namespace TestExecution
|
|
{
|
|
template<>
|
|
class VerifyOutputTraits<TextAttribute>
|
|
{
|
|
public:
|
|
static WEX::Common::NoThrowString ToString(const TextAttribute& attr)
|
|
{
|
|
return WEX::Common::NoThrowString().Format(
|
|
L"{FG:%s,BG:%s,bold:%d,wLegacy:(0x%04x)}",
|
|
VerifyOutputTraits<TextColor>::ToString(attr._foreground).GetBuffer(),
|
|
VerifyOutputTraits<TextColor>::ToString(attr._background).GetBuffer(),
|
|
attr.IsBold(),
|
|
attr._wAttrLegacy);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
#endif
|