diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index 55e3b15a5ea..93693ea2cfb 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -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); @@ -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; @@ -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); @@ -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); @@ -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); @@ -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: @@ -404,5 +462,6 @@ bool TextAttribute::BackgroundIsDefault() const noexcept void TextAttribute::SetStandardErase() noexcept { _attrs = CharacterAttributes::Normal; + _underlineStyle = UnderlineStyle::NoUnderline; _hyperlinkId = 0; } diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index 2a61e8902de..5e210678690 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -27,6 +27,16 @@ 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: @@ -34,7 +44,10 @@ class TextAttribute final _attrs{ CharacterAttributes::Normal }, _foreground{}, _background{}, - _hyperlinkId{ 0 } + _hyperlinkId{ 0 }, + _underlineColor{}, + _underlineStyle{ UnderlineStyle::NoUnderline }, + __unused{} { } @@ -42,7 +55,10 @@ class TextAttribute final _attrs{ gsl::narrow_cast(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{} { } @@ -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{} { } @@ -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; @@ -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; @@ -131,6 +165,7 @@ class TextAttribute final void SetDefaultForeground() noexcept; void SetDefaultBackground() noexcept; + void SetDefaultUnderlineColor() noexcept; void SetDefaultRenditionAttributes() noexcept; bool BackgroundIsDefault() const noexcept; @@ -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; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 42f8d02f4bf..3616b61eeb2 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -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(bufferSize, TextAttribute{}, 0, // temporarily set size to 0 so it won't render. diff --git a/src/renderer/vt/VtSequences.cpp b/src/renderer/vt/VtSequences.cpp index f22d76234ff..98ee7a8020d 100644 --- a/src/renderer/vt/VtSequences.cpp +++ b/src/renderer/vt/VtSequences.cpp @@ -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. @@ -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. @@ -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: @@ -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: diff --git a/src/renderer/vt/Xterm256Engine.cpp b/src/renderer/vt/Xterm256Engine.cpp index dc506322ccf..58f529fba2a 100644 --- a/src/renderer/vt/Xterm256Engine.cpp +++ b/src/renderer/vt/Xterm256Engine.cpp @@ -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()) { diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index dda813b92fe..c4ea6dfd277 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -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 @@ -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) @@ -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; } diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index 3ba1e702af6..00eb01269db 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -182,6 +182,10 @@ namespace Microsoft::Console::Render const bool fIsForeground) noexcept; [[nodiscard]] HRESULT _SetGraphicsRenditionDefaultColor(const bool fIsForeground) noexcept; + [[nodiscard]] HRESULT _SetGraphicsRenditionUnderline256Color(const BYTE index) noexcept; + [[nodiscard]] HRESULT _SetGraphicsRenditionUnderlineRGBColor(const COLORREF color) noexcept; + [[nodiscard]] HRESULT _SetGraphicsRenditionUnderlineDefaultColor() noexcept; + [[nodiscard]] HRESULT _SetGraphicsDefault() noexcept; [[nodiscard]] HRESULT _ResizeWindow(const til::CoordType sWidth, const til::CoordType sHeight) noexcept; @@ -189,6 +193,7 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _SetIntense(const bool isIntense) noexcept; [[nodiscard]] HRESULT _SetFaint(const bool isFaint) noexcept; [[nodiscard]] HRESULT _SetUnderlined(const bool isUnderlined) noexcept; + [[nodiscard]] HRESULT _SetUnderlineExtended(const UnderlineStyle style) noexcept; [[nodiscard]] HRESULT _SetDoublyUnderlined(const bool isUnderlined) noexcept; [[nodiscard]] HRESULT _SetOverlined(const bool isOverlined) noexcept; [[nodiscard]] HRESULT _SetItalic(const bool isItalic) noexcept; diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index feeb0413979..612f82c093f 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -398,7 +398,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes // as well as the Faint/Blink options. RGBColorOrFaint = 2, // 2 is also Faint, decreased intensity (ISO 6429). Italics = 3, - Underline = 4, + Underline = 4, // same for extended underline styles `SGR 4:x`. BlinkOrXterm256Index = 5, // 5 is also Blink. RapidBlink = 6, Negative = 7, @@ -434,6 +434,8 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes BackgroundDefault = 49, Overline = 53, NoOverline = 55, + UnderlineColor = 58, + UnderlineColorDefault = 59, BrightForegroundBlack = 90, BrightForegroundRed = 91, BrightForegroundGreen = 92, diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 92af043e4f6..8e82d50260a 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -77,7 +77,7 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string) auto& cursor = textBuffer.GetCursor(); auto cursorPosition = cursor.GetPosition(); const auto wrapAtEOL = _api.GetSystemMode(ITerminalApi::Mode::AutoWrap); - const auto attributes = textBuffer.GetCurrentAttributes(); + const auto& attributes = textBuffer.GetCurrentAttributes(); const auto viewport = _api.GetViewport(); const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true); @@ -502,7 +502,7 @@ bool AdaptDispatch::CursorSaveState() // First retrieve some information about the buffer const auto viewport = _api.GetViewport(); const auto& textBuffer = _api.GetTextBuffer(); - const auto attributes = textBuffer.GetCurrentAttributes(); + const auto& attributes = textBuffer.GetCurrentAttributes(); // The cursor is given to us by the API as relative to the whole buffer. // But in VT speak, the cursor row should be relative to the current viewport top. @@ -1012,6 +1012,16 @@ void AdaptDispatch::_ChangeRectAttributes(TextBuffer& textBuffer, const til::rec { attr.SetBackground(*changeOps.background); } + // remove underline style if the underline is no longer set. + if (!attr.IsUnderlined()) + { + attr.SetUnderlineStyle(UnderlineStyle::NoUnderline); + } + // incase of DECCARA, we might have received a new style. (we never pass ChangeOps with underlineStyle set in DECRARA) + else if (changeOps.underlineStyle) + { + attr.SetUnderlineStyle(*changeOps.underlineStyle); + } rowBuffer.ReplaceAttributes(col, col + 1, attr); } } @@ -1144,6 +1154,14 @@ bool AdaptDispatch::ChangeAttributesRectangularArea(const VTInt top, const VTInt changeOps.foreground = foregroundChanged ? std::optional{ foreground } : std::nullopt; changeOps.background = backgroundChanged ? std::optional{ background } : std::nullopt; + const auto underlineColor = allAttrsOff.GetUnderlineColor(); + const auto underlineColorChanged = !underlineColor.IsDefault() || allAttrsOn.GetUnderlineColor().IsDefault(); + changeOps.underlineColor = underlineColorChanged ? std::optional{ underlineColor } : std::nullopt; + + const auto underlineStyle = allAttrsOff.GetUnderlineStyle(); + const auto underlineStyleChanged = allAttrsOff.IsUnderlined() || !allAttrsOn.IsUnderlined(); + changeOps.underlineStyle = underlineStyleChanged ? std::optional{ underlineStyle } : std::nullopt; + _ChangeRectOrStreamAttributes({ left, top, right, bottom }, changeOps); return true; @@ -4165,7 +4183,7 @@ void AdaptDispatch::_ReportSGRSetting() const fmt::basic_memory_buffer response; response.append(L"\033P1$r0"sv); - const auto attr = _api.GetTextBuffer().GetCurrentAttributes(); + const auto& attr = _api.GetTextBuffer().GetCurrentAttributes(); // For each boolean attribute that is set, we add the appropriate // parameter value to the response string. const auto addAttribute = [&](const auto& parameter, const auto enabled) { @@ -4277,7 +4295,7 @@ void AdaptDispatch::_ReportDECSCASetting() const fmt::basic_memory_buffer response; response.append(L"\033P1$r"sv); - const auto attr = _api.GetTextBuffer().GetCurrentAttributes(); + const auto& attr = _api.GetTextBuffer().GetCurrentAttributes(); response.append(attr.IsProtected() ? L"1"sv : L"0"sv); // The '"q' indicates this is an DECSCA response, and ST ends the sequence. @@ -4299,7 +4317,6 @@ void AdaptDispatch::_ReportDECSACESetting() const fmt::basic_memory_buffer response; response.append(L"\033P1$r"sv); - const auto attr = _api.GetTextBuffer().GetCurrentAttributes(); response.append(_modes.test(Mode::RectangularChangeExtent) ? L"2"sv : L"1"sv); // The '*x' indicates this is an DECSACE response, and ST ends the sequence. @@ -4399,7 +4416,7 @@ void AdaptDispatch::_ReportCursorInformation() const auto viewport = _api.GetViewport(); const auto& textBuffer = _api.GetTextBuffer(); const auto& cursor = textBuffer.GetCursor(); - const auto attributes = textBuffer.GetCurrentAttributes(); + const auto& attributes = textBuffer.GetCurrentAttributes(); // First pull the cursor position relative to the entire buffer out of the console. til::point cursorPosition{ cursor.GetPosition() }; diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 7417f6f82fd..1c89cc99348 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -207,6 +207,8 @@ namespace Microsoft::Console::VirtualTerminal CharacterAttributes xorAttrMask = CharacterAttributes::Normal; std::optional foreground; std::optional background; + std::optional underlineColor; + std::optional underlineStyle; }; void _WriteToBuffer(const std::wstring_view string); @@ -296,6 +298,7 @@ namespace Microsoft::Console::VirtualTerminal SgrStack _sgrStack; + void _SetUnderlineStyleHelper(const VTParameter option, TextAttribute& attr) noexcept; size_t _SetRgbColorsHelper(const VTParameters options, TextAttribute& attr, const bool isForeground) noexcept; diff --git a/src/terminal/adapter/adaptDispatchGraphics.cpp b/src/terminal/adapter/adaptDispatchGraphics.cpp index a6567abd09e..beaeb7e4c42 100644 --- a/src/terminal/adapter/adaptDispatchGraphics.cpp +++ b/src/terminal/adapter/adaptDispatchGraphics.cpp @@ -12,6 +12,23 @@ using namespace Microsoft::Console::VirtualTerminal; using namespace Microsoft::Console::VirtualTerminal::DispatchTypes; +// Routine Description: +// - Helper to parse the underline style option. +// Arguments: +// - options - An option that will be used to interpret the underline style. +// - attr - The attribute that will be updated with the parsed underline style. +// Return Value: +// - +void AdaptDispatch::_SetUnderlineStyleHelper(const VTParameter option, TextAttribute& attr) noexcept +{ + const auto style = option.value_or(0); + // Only apply the style if it's one of the valid underline styles (0-5). + if ((style >= 0) && (style <= 5)) + { + attr.SetUnderlineStyle(gsl::narrow_cast(style)); + } +} + // Routine Description: // - Helper to parse extended graphics options, which start with 38 (FG) or 48 (BG) // These options are followed by either a 2 (RGB) or 5 (xterm index) @@ -67,14 +84,14 @@ size_t AdaptDispatch::_SetRgbColorsHelper(const VTParameters options, } // Routine Description: -// - Helper to parse extended graphics options, which start with 38 (FG) or 48 (BG) +// - Helper to parse extended graphics options, which start with 38 (FG) or 48 (BG) or 58 (UL) // - These options are followed by either a 2 (RGB) or 5 (xterm index): // - RGB sequences then take 4 MORE options to designate the ColorSpaceID, R, G, B parts // of the color. // - Xterm index will use the option that follows to use a color from the // preset 256 color xterm color table. // Arguments: -// - colorItem - One of FG(38) and BG(48), indicating which color we're setting. +// - colorItem - One of the FG(38), BG(48), UL(58), indicating which color we're setting. // - options - An array of options that will be used to generate the RGB color // - attr - The attribute that will be updated with the parsed color. // Return Value: @@ -83,24 +100,36 @@ void AdaptDispatch::_SetRgbColorsHelperFromSubParams(const VTParameter colorItem const VTSubParameters options, TextAttribute& attr) noexcept { - // This should be called for applying FG and BG colors only. - assert(colorItem == GraphicsOptions::ForegroundExtended || - colorItem == GraphicsOptions::BackgroundExtended); + const auto applyColor = [&](const TextColor& color) { + switch (colorItem) + { + case ForegroundExtended: + attr.SetForeground(color); + break; + case BackgroundExtended: + attr.SetBackground(color); + break; + case UnderlineColor: + attr.SetUnderlineColor(color); + break; + default: + break; + }; + }; - const bool isForeground = (colorItem == GraphicsOptions::ForegroundExtended); const DispatchTypes::GraphicsOptions typeOpt = options.at(0); - - if (typeOpt == DispatchTypes::GraphicsOptions::RGBColorOrFaint) + switch (typeOpt) + { + case DispatchTypes::GraphicsOptions::RGBColorOrFaint: { // sub params are in the order: // :2:::: - // We treat a color as invalid, if it has a color space ID, as some - // applications that support non-standard ODA color sequence may send - // the red value in its place. + // We treat a color as invalid if it has a non-empty color space ID, as + // some applications that support non-standard ODA color sequence might + // send the red value in its place. const bool hasColorSpaceId = options.at(1).has_value(); - // Skip color-space-id. const size_t red = options.at(2).value_or(0); const size_t green = options.at(3).value_or(0); const size_t blue = options.at(4).value_or(0); @@ -109,11 +138,11 @@ void AdaptDispatch::_SetRgbColorsHelperFromSubParams(const VTParameter colorItem // This is to match XTerm's and VTE's behavior. if (!hasColorSpaceId && red <= 255 && green <= 255 && blue <= 255) { - const auto rgbColor = RGB(red, green, blue); - attr.SetColor(rgbColor, isForeground); + applyColor(TextColor{ RGB(red, green, blue) }); } + break; } - else if (typeOpt == DispatchTypes::GraphicsOptions::BlinkOrXterm256Index) + case DispatchTypes::GraphicsOptions::BlinkOrXterm256Index: { // sub params are in the order: // :5: @@ -125,16 +154,13 @@ void AdaptDispatch::_SetRgbColorsHelperFromSubParams(const VTParameter colorItem if (tableIndex <= 255) { const auto adjustedIndex = gsl::narrow_cast(tableIndex); - if (isForeground) - { - attr.SetIndexedForeground256(adjustedIndex); - } - else - { - attr.SetIndexedBackground256(adjustedIndex); - } + applyColor(TextColor{ adjustedIndex, true }); } + break; } + default: + break; + }; } // Routine Description: @@ -164,6 +190,7 @@ size_t AdaptDispatch::_ApplyGraphicsOption(const VTParameters options, case Off: attr.SetDefaultForeground(); attr.SetDefaultBackground(); + attr.SetDefaultUnderlineColor(); attr.SetDefaultRenditionAttributes(); return 1; case ForegroundDefault: @@ -172,6 +199,9 @@ size_t AdaptDispatch::_ApplyGraphicsOption(const VTParameters options, case BackgroundDefault: attr.SetDefaultBackground(); return 1; + case UnderlineColorDefault: + attr.SetDefaultUnderlineColor(); + return 1; case Intense: attr.SetIntense(true); return 1; @@ -213,7 +243,7 @@ size_t AdaptDispatch::_ApplyGraphicsOption(const VTParameters options, case Positive: attr.SetReverseVideo(false); return 1; - case Underline: + case Underline: // SGR 4 (without extended styling) attr.SetUnderlined(true); return 1; case DoublyUnderlined: @@ -351,8 +381,12 @@ void AdaptDispatch::_ApplyGraphicsOptionWithSubParams(const VTParameter option, // we should just skip over them. switch (option) { + case Underline: + _SetUnderlineStyleHelper(subParams.at(0), attr); + break; case ForegroundExtended: case BackgroundExtended: + case UnderlineColor: _SetRgbColorsHelperFromSubParams(option, subParams, attr); break; default: @@ -437,7 +471,7 @@ bool AdaptDispatch::SetCharacterProtectionAttribute(const VTParameters options) // - True. bool AdaptDispatch::PushGraphicsRendition(const VTParameters options) { - const auto currentAttributes = _api.GetTextBuffer().GetCurrentAttributes(); + const auto& currentAttributes = _api.GetTextBuffer().GetCurrentAttributes(); _sgrStack.Push(currentAttributes, options); return true; } @@ -451,7 +485,7 @@ bool AdaptDispatch::PushGraphicsRendition(const VTParameters options) // - True. bool AdaptDispatch::PopGraphicsRendition() { - const auto currentAttributes = _api.GetTextBuffer().GetCurrentAttributes(); + const auto& currentAttributes = _api.GetTextBuffer().GetCurrentAttributes(); _api.SetTextAttributes(_sgrStack.Pop(currentAttributes)); return true; }