// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "inc/sgrStack.hpp" using namespace Microsoft::Console::VirtualTerminal::DispatchTypes; namespace Microsoft::Console::VirtualTerminal { SgrStack::SgrStack() noexcept : _nextPushIndex{ 0 }, _numSavedAttrs{ 0 } { } void SgrStack::Push(const TextAttribute& currentAttributes, const VTParameters options) noexcept { AttrBitset validParts; try { if (options.empty()) { // We save all current attributes. validParts.set(static_cast(SgrSaveRestoreStackOptions::All)); } else { // Each option is encoded as a bit in validParts. All options (that fit) are // encoded; options that aren't supported are ignored when read back (popped). // So if you try to save only unsupported aspects of the current text // attributes, you'll do what is effectively an "empty" push (the subsequent // pop will not change the current attributes), which is the correct behavior. for (size_t i = 0; i < options.size(); i++) { const size_t optionAsIndex = options.at(i).value_or(0); // Options must be specified singly; not in combination. Values that are // out of range will be ignored. if (optionAsIndex < validParts.size()) { validParts.set(optionAsIndex); } } } } catch (...) { // The static analyzer knows that the bitset operations can throw // std::out_of_range. However, we know that won't happen, because we pre-check // that everything should be in range. So we plan to never execute this // failfast: FAIL_FAST_CAUGHT_EXCEPTION(); } if (_numSavedAttrs < gsl::narrow(_storedSgrAttributes.size())) { _numSavedAttrs++; } _storedSgrAttributes.at(_nextPushIndex) = { currentAttributes, validParts }; _nextPushIndex = (_nextPushIndex + 1) % gsl::narrow(_storedSgrAttributes.size()); } const TextAttribute SgrStack::Pop(const TextAttribute& currentAttributes) noexcept { if (_numSavedAttrs > 0) { _numSavedAttrs--; if (_nextPushIndex == 0) { _nextPushIndex = gsl::narrow(_storedSgrAttributes.size() - 1); } else { _nextPushIndex--; } SavedSgrAttributes& restoreMe = _storedSgrAttributes.at(_nextPushIndex); try { if (restoreMe.ValidParts.test(static_cast(SgrSaveRestoreStackOptions::All))) { return restoreMe.TextAttributes; } else { return _CombineWithCurrentAttributes(currentAttributes, restoreMe.TextAttributes, restoreMe.ValidParts); } } catch (...) { // The static analyzer knows that the bitset operations can throw // std::out_of_range. However, we know that won't happen, because we // pre-check that everything should be in range. So we plan to never // execute this failfast: FAIL_FAST_CAUGHT_EXCEPTION(); } } return currentAttributes; } TextAttribute SgrStack::_CombineWithCurrentAttributes(const TextAttribute& currentAttributes, const TextAttribute& savedAttribute, const AttrBitset validParts) // of savedAttribute { // If we are restoring all attributes, we should have just taken savedAttribute // before we even got here. FAIL_FAST_IF(validParts.test(static_cast(SgrSaveRestoreStackOptions::All))); TextAttribute result = currentAttributes; // From xterm documentation: // // CSI # { // CSI Ps ; Ps # { // Push video attributes onto stack (XTPUSHSGR), xterm. The // optional parameters correspond to the SGR encoding for video // attributes, except for colors (which do not have a unique SGR // code): // Ps = 1 -> Bold. // Ps = 2 -> Faint. // Ps = 3 -> Italicized. // Ps = 4 -> Underlined. // Ps = 5 -> Blink. // Ps = 7 -> Inverse. // Ps = 8 -> Invisible. // Ps = 9 -> Crossed-out characters. // Ps = 2 1 -> Doubly-underlined. // Ps = 3 0 -> Foreground color. // Ps = 3 1 -> Background color. // // (some closing braces for people with editors that get thrown off without them: }}) // Boldness = 1, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::Boldness))) { result.SetBold(savedAttribute.IsBold()); } // Faintness = 2, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::Faintness))) { result.SetFaint(savedAttribute.IsFaint()); } // Italics = 3, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::Italics))) { result.SetItalic(savedAttribute.IsItalic()); } // Underline = 4, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::Underline))) { result.SetUnderlined(savedAttribute.IsUnderlined()); } // Blink = 5, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::Blink))) { result.SetBlinking(savedAttribute.IsBlinking()); } // Negative = 7, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::Negative))) { result.SetReverseVideo(savedAttribute.IsReverseVideo()); } // Invisible = 8, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::Invisible))) { result.SetInvisible(savedAttribute.IsInvisible()); } // CrossedOut = 9, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::CrossedOut))) { result.SetCrossedOut(savedAttribute.IsCrossedOut()); } // DoublyUnderlined = 21, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::DoublyUnderlined))) { result.SetDoublyUnderlined(savedAttribute.IsDoublyUnderlined()); } // SaveForegroundColor = 30, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::SaveForegroundColor))) { result.SetForeground(savedAttribute.GetForeground()); } // SaveBackgroundColor = 31, if (validParts.test(static_cast(SgrSaveRestoreStackOptions::SaveBackgroundColor))) { result.SetBackground(savedAttribute.GetBackground()); } return result; } }