Skip to content

Commit

Permalink
PRE-MERGE #15795 Add support for underline style and color
Browse files Browse the repository at this point in the history
  • Loading branch information
zadjii-msft committed Aug 8, 2023
2 parents a00af06 + 1027e1b commit d5e2ccc
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 44 deletions.
65 changes: 62 additions & 3 deletions src/buffer/out/TextAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

// Keeping TextColor compact helps us keeping TextAttribute compact,
// which in turn ensures that our buffer memory usage is low.
static_assert(sizeof(TextAttribute) == 12);
static_assert(sizeof(TextAttribute) == 18);
static_assert(alignof(TextAttribute) == 2);
// Ensure that we can memcpy() and memmove() the struct for performance.
static_assert(std::is_trivially_copyable_v<TextAttribute>);
Expand Down Expand Up @@ -156,6 +156,16 @@ uint16_t TextAttribute::GetHyperlinkId() const noexcept
return _hyperlinkId;
}

TextColor TextAttribute::GetUnderlineColor() const noexcept
{
return _underlineColor;
}

UnderlineStyle TextAttribute::GetUnderlineStyle() const noexcept
{
return _underlineStyle;
}

void TextAttribute::SetForeground(const TextColor foreground) noexcept
{
_foreground = foreground;
Expand All @@ -166,6 +176,11 @@ void TextAttribute::SetBackground(const TextColor background) noexcept
_background = background;
}

void TextAttribute::SetUnderlineColor(const TextColor color) noexcept
{
_underlineColor = color;
}

void TextAttribute::SetForeground(const COLORREF rgbForeground) noexcept
{
_foreground = TextColor(rgbForeground);
Expand Down Expand Up @@ -277,6 +292,9 @@ bool TextAttribute::IsCrossedOut() const noexcept
return WI_IsFlagSet(_attrs, CharacterAttributes::CrossedOut);
}

// Method description:
// - Returns true if the text is underlined with the any underline style,
// Eg. singly, doubly, curly, dotted, and dashed.
bool TextAttribute::IsUnderlined() const noexcept
{
return WI_IsFlagSet(_attrs, CharacterAttributes::Underlined);
Expand Down Expand Up @@ -334,9 +352,43 @@ void TextAttribute::SetCrossedOut(bool isCrossedOut) noexcept

void TextAttribute::SetUnderlined(bool isUnderlined) noexcept
{
WI_UpdateFlag(_attrs, CharacterAttributes::Underlined, isUnderlined);
if (isUnderlined)
{
SetUnderlineStyle(UnderlineStyle::SinglyUnderlined);
}
else
{
SetUnderlineStyle(UnderlineStyle::NoUnderline);
}
}

void TextAttribute::SetUnderlineStyle(const UnderlineStyle underlineStyle) noexcept
{
switch (underlineStyle)
{
case UnderlineStyle::NoUnderline:
WI_ClearFlag(_attrs, CharacterAttributes::Underlined);
_underlineStyle = underlineStyle;
break;
case UnderlineStyle::SinglyUnderlined:
case UnderlineStyle::DoublyUnderlined:
case UnderlineStyle::CurlyUnderlined:
case UnderlineStyle::DottedUnderlined:
case UnderlineStyle::DashedUnderlined:
WI_SetFlag(_attrs, CharacterAttributes::Underlined);
_underlineStyle = underlineStyle;
break;
default:
/* do nothing */
break;
}
}

// Method description:
// - Sets doubly underlined in the attribute.
// - This is used for SGR 21 sequences. (do not use with SGR 4:x)
// Arguments:
// - isDoublyUnderlined - whether to set doubly underlined or not.
void TextAttribute::SetDoublyUnderlined(bool isDoublyUnderlined) noexcept
{
WI_UpdateFlag(_attrs, CharacterAttributes::DoublyUnderlined, isDoublyUnderlined);
Expand Down Expand Up @@ -374,12 +426,18 @@ void TextAttribute::SetDefaultBackground() noexcept
_background = TextColor();
}

void TextAttribute::SetDefaultUnderlineColor() noexcept
{
_underlineColor = TextColor{};
}

// Method description:
// - Resets only the rendition character attributes, which includes everything
// except the Protected attribute.
// except the Protected attribute. Additionally, resets the underline style.
void TextAttribute::SetDefaultRenditionAttributes() noexcept
{
_attrs &= ~CharacterAttributes::Rendition;
_underlineStyle = UnderlineStyle::NoUnderline;
}

// Method Description:
Expand All @@ -404,5 +462,6 @@ bool TextAttribute::BackgroundIsDefault() const noexcept
void TextAttribute::SetStandardErase() noexcept
{
_attrs = CharacterAttributes::Normal;
_underlineStyle = UnderlineStyle::NoUnderline;
_hyperlinkId = 0;
}
44 changes: 41 additions & 3 deletions src/buffer/out/TextAttribute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,38 @@ Revision History:
#include "WexTestClass.h"
#endif

enum class UnderlineStyle : uint8_t
{
NoUnderline = 0,
SinglyUnderlined = 1,
DoublyUnderlined = 2,
CurlyUnderlined = 3,
DottedUnderlined = 4,
DashedUnderlined = 5,
};

class TextAttribute final
{
public:
constexpr TextAttribute() noexcept :
_attrs{ CharacterAttributes::Normal },
_foreground{},
_background{},
_hyperlinkId{ 0 }
_hyperlinkId{ 0 },
_underlineColor{},
_underlineStyle{ UnderlineStyle::NoUnderline },
__unused{}
{
}

explicit constexpr TextAttribute(const WORD wLegacyAttr) noexcept :
_attrs{ gsl::narrow_cast<WORD>(wLegacyAttr & USED_META_ATTRS) },
_foreground{ gsl::at(s_legacyForegroundColorMap, wLegacyAttr & FG_ATTRS) },
_background{ gsl::at(s_legacyBackgroundColorMap, (wLegacyAttr & BG_ATTRS) >> 4) },
_hyperlinkId{ 0 }
_hyperlinkId{ 0 },
_underlineColor{},
_underlineStyle{ UnderlineStyle::NoUnderline },
__unused{}
{
}

Expand All @@ -51,7 +67,21 @@ class TextAttribute final
_attrs{ CharacterAttributes::Normal },
_foreground{ rgbForeground },
_background{ rgbBackground },
_hyperlinkId{ 0 }
_hyperlinkId{ 0 },
_underlineColor{},
_underlineStyle{ UnderlineStyle::NoUnderline },
__unused{}
{
}

constexpr TextAttribute(const CharacterAttributes attrs, const TextColor foreground, const TextColor background, const uint16_t hyperlinkId, const TextColor underlineColor, const UnderlineStyle underlineStyle) noexcept :
_attrs{ attrs },
_foreground{ foreground },
_background{ background },
_hyperlinkId{ hyperlinkId },
_underlineColor{ underlineColor },
_underlineStyle{ underlineStyle },
__unused{}
{
}

Expand Down Expand Up @@ -99,6 +129,7 @@ class TextAttribute final
void SetInvisible(bool isInvisible) noexcept;
void SetCrossedOut(bool isCrossedOut) noexcept;
void SetUnderlined(bool isUnderlined) noexcept;
void SetUnderlineStyle(const UnderlineStyle underlineStyle) noexcept;
void SetDoublyUnderlined(bool isDoublyUnderlined) noexcept;
void SetOverlined(bool isOverlined) noexcept;
void SetReverseVideo(bool isReversed) noexcept;
Expand All @@ -118,8 +149,11 @@ class TextAttribute final
TextColor GetForeground() const noexcept;
TextColor GetBackground() const noexcept;
uint16_t GetHyperlinkId() const noexcept;
TextColor GetUnderlineColor() const noexcept;
UnderlineStyle GetUnderlineStyle() const noexcept;
void SetForeground(const TextColor foreground) noexcept;
void SetBackground(const TextColor background) noexcept;
void SetUnderlineColor(const TextColor color) noexcept;
void SetForeground(const COLORREF rgbForeground) noexcept;
void SetBackground(const COLORREF rgbBackground) noexcept;
void SetIndexedForeground(const BYTE fgIndex) noexcept;
Expand All @@ -131,6 +165,7 @@ class TextAttribute final

void SetDefaultForeground() noexcept;
void SetDefaultBackground() noexcept;
void SetDefaultUnderlineColor() noexcept;
void SetDefaultRenditionAttributes() noexcept;

bool BackgroundIsDefault() const noexcept;
Expand Down Expand Up @@ -175,6 +210,9 @@ class TextAttribute final
uint16_t _hyperlinkId; // sizeof: 2, alignof: 2
TextColor _foreground; // sizeof: 4, alignof: 1
TextColor _background; // sizeof: 4, alignof: 1
TextColor _underlineColor; // sizeof: 4, alignof: 1
UnderlineStyle _underlineStyle; // sizeof: 1, alignof: 1
BYTE __unused; // sizeof: 1, alignof: 1 (avoids padding)

#ifdef UNIT_TESTING
friend class TextBufferTests;
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept
// but after the resize, we'll want to make sure that the new buffer's
// current attributes (the ones used for printing new text) match the
// old buffer's.
const auto oldBufferAttributes = _mainBuffer->GetCurrentAttributes();
const auto& oldBufferAttributes = _mainBuffer->GetCurrentAttributes();
newTextBuffer = std::make_unique<TextBuffer>(bufferSize,
TextAttribute{},
0, // temporarily set size to 0 so it won't render.
Expand Down
68 changes: 68 additions & 0 deletions src/renderer/vt/VtSequences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,19 @@ using namespace Microsoft::Console::Render;
return _WriteFormatted(FMT_COMPILE("\x1b[{}8;5;{}m"), fIsForeground ? '3' : '4', index);
}

// Method Description:
// - Formats and writes a sequence to change the current underline color to an
// indexed color from the 256-color table.
// - Uses sub parameters.
// Arguments:
// - index: color table index to emit as a VT sequence
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderline256Color(const BYTE index) noexcept
{
return _WriteFormatted(FMT_COMPILE("\x1b[58:5:{}m"), index);
}

// Method Description:
// - Formats and writes a sequence to change the current text attributes to an
// RGB color.
Expand All @@ -258,6 +271,22 @@ using namespace Microsoft::Console::Render;
return _WriteFormatted(FMT_COMPILE("\x1b[{}8;2;{};{};{}m"), fIsForeground ? '3' : '4', r, g, b);
}

// Method Description:
// - Formats and writes a sequence to change the current underline color to an
// RGB color.
// - Uses sub parameters.
// Arguments:
// - color: The color to emit a VT sequence for.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderlineRGBColor(const COLORREF color) noexcept
{
const auto r = GetRValue(color);
const auto g = GetGValue(color);
const auto b = GetBValue(color);
return _WriteFormatted(FMT_COMPILE("\x1b[58:2::{}:{}:{}m"), r, g, b);
}

// Method Description:
// - Formats and writes a sequence to change the current text attributes to the
// default foreground or background. Does not affect the intensity of text.
Expand All @@ -270,6 +299,16 @@ using namespace Microsoft::Console::Render;
return _Write(fIsForeground ? ("\x1b[39m") : ("\x1b[49m"));
}

// Method Description:
// - Formats and writes a sequence to change the current underline color to the
// default color.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_SetGraphicsRenditionUnderlineDefaultColor() noexcept
{
return _Write("\x1b[59m");
}

// Method Description:
// - Formats and writes a sequence to change the terminal's window size.
// Arguments:
Expand Down Expand Up @@ -332,6 +371,35 @@ using namespace Microsoft::Console::Render;
return _Write(isFaint ? "\x1b[2m" : "\x1b[22m");
}

// Method Description:
// - Formats and writes a sequence to change the extended underline styling of the following text.
// - Uses backward compatible SGR 4 (without sub parameter) and SGR 21 for single and doubly underline.
// Arguments:
// - style: underline style to use.
// Return Value:
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_SetUnderlineExtended(const UnderlineStyle style) noexcept
{
switch (style)
{
// we don't expect style to be NoUnderline here, but we'll handle it anyway.
case UnderlineStyle::NoUnderline:
return _SetUnderlined(false);
case UnderlineStyle::SinglyUnderlined:
return _SetUnderlined(true);
case UnderlineStyle::DoublyUnderlined:
return _SetDoublyUnderlined(true);
case UnderlineStyle::CurlyUnderlined:
return _Write("\x1b[4:3m");
case UnderlineStyle::DottedUnderlined:
return _Write("\x1b[4:4m");
case UnderlineStyle::DashedUnderlined:
return _Write("\x1b[4:5m");
default:
return S_OK;
}
}

// Method Description:
// - Formats and writes a sequence to change the underline of the following text.
// Arguments:
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/vt/Xterm256Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ Xterm256Engine::Xterm256Engine(_In_ wil::unique_hfile hPipe,
// we can then check if either should be turned back on again.
if (textAttributes.IsUnderlined() && !_lastTextAttributes.IsUnderlined())
{
RETURN_IF_FAILED(_SetUnderlined(true));
_lastTextAttributes.SetUnderlined(true);
auto style = textAttributes.GetUnderlineStyle();
RETURN_IF_FAILED(_SetUnderlineExtended(style));
_lastTextAttributes.SetUnderlineStyle(style);
}
if (textAttributes.IsDoublyUnderlined() && !_lastTextAttributes.IsDoublyUnderlined())
{
Expand Down
29 changes: 27 additions & 2 deletions src/renderer/vt/paint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,13 @@ using namespace Microsoft::Console::Types;
{
const auto fg = textAttributes.GetForeground();
const auto bg = textAttributes.GetBackground();
const auto ul = textAttributes.GetUnderlineColor();
auto lastFg = _lastTextAttributes.GetForeground();
auto lastBg = _lastTextAttributes.GetBackground();
auto lastUl = _lastTextAttributes.GetUnderlineColor();

// If both the FG and BG should be the defaults, emit a SGR reset.
if (fg.IsDefault() && bg.IsDefault() && !(lastFg.IsDefault() && lastBg.IsDefault()))
// If the FG, BG and UL should be the defaults, emit a SGR reset.
if (fg.IsDefault() && bg.IsDefault() && ul.IsDefault() && !(lastFg.IsDefault() && lastBg.IsDefault() && lastUl.IsDefault()))
{
// SGR Reset will clear all attributes (except hyperlink ID) - which means
// we cannot reset _lastTextAttributes by simply doing
Expand All @@ -270,9 +272,11 @@ using namespace Microsoft::Console::Types;
RETURN_IF_FAILED(_SetGraphicsDefault());
_lastTextAttributes.SetDefaultBackground();
_lastTextAttributes.SetDefaultForeground();
_lastTextAttributes.SetDefaultUnderlineColor();
_lastTextAttributes.SetDefaultRenditionAttributes();
lastFg = {};
lastBg = {};
lastUl = {};
}

if (fg != lastFg)
Expand Down Expand Up @@ -317,6 +321,27 @@ using namespace Microsoft::Console::Types;
_lastTextAttributes.SetBackground(bg);
}

if (ul != lastUl)
{
if (ul.IsDefault())
{
RETURN_IF_FAILED(_SetGraphicsRenditionUnderlineDefaultColor());
}
else if (ul.IsIndex16()) // underline can't be 16 color
{
/* do nothing */
}
else if (ul.IsIndex256())
{
RETURN_IF_FAILED(_SetGraphicsRenditionUnderline256Color(ul.GetIndex()));
}
else if (ul.IsRgb())
{
RETURN_IF_FAILED(_SetGraphicsRenditionUnderlineRGBColor(ul.GetRGB()));
}
_lastTextAttributes.SetUnderlineColor(ul);
}

return S_OK;
}

Expand Down
Loading

0 comments on commit d5e2ccc

Please sign in to comment.