From c52dca40d27197a24bcbae9a48baf83a61429484 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 2 May 2024 21:38:04 +0200 Subject: [PATCH] Fix doskey macros for inputs with >1 consecutive whitespace (#17129) Initially the PR restored the original v1 conhost code for `MatchAndCopyAlias` which fixed the linked issue. Afterwards, I've taken the liberty to rewrite the code to use modern constructs again, primarily `string_view`. Additionally, the v1 code first counted the number of arguments and then iterated through it again to assemble them. This new code does both things at once. Closes #15736 ## Validation Steps Performed The unit tests have been extended to cover multiple consecutive spaces. All tests pass. --- src/host/alias.cpp | 425 ++++++++---------------------- src/host/alias.h | 39 +-- src/host/ut_host/AliasTests.cpp | 261 +----------------- src/inc/til.h | 4 +- src/renderer/atlas/BackendD3D.cpp | 2 +- 5 files changed, 118 insertions(+), 613 deletions(-) diff --git a/src/host/alias.cpp b/src/host/alias.cpp index a82fa198761..a658e2218d6 100644 --- a/src/host/alias.cpp +++ b/src/host/alias.cpp @@ -21,7 +21,9 @@ using Microsoft::Console::Interactivity::ServiceLocator; struct case_insensitive_hash { - std::size_t operator()(const std::wstring& key) const + using is_transparent = void; + + std::size_t operator()(const std::wstring_view& key) const { til::hasher h; for (const auto& ch : key) @@ -34,9 +36,11 @@ struct case_insensitive_hash struct case_insensitive_equality { - bool operator()(const std::wstring& lhs, const std::wstring& rhs) const + using is_transparent = void; + + bool operator()(const std::wstring_view& lhs, const std::wstring_view& rhs) const { - return 0 == _wcsicmp(lhs.data(), rhs.data()); + return til::compare_ordinal_insensitive(lhs, rhs) == 0; } }; @@ -156,10 +160,10 @@ std::unordered_mapsecond; + const auto& exeData = exeIter->second; const auto sourceIter = exeData.find(sourceString); RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), sourceIter == exeData.end()); - const auto targetString = sourceIter->second; + const auto& targetString = sourceIter->second; RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), targetString.size() == 0); // TargetLength is a byte count, convert to characters. @@ -333,7 +337,7 @@ static std::wstring aliasesSeparator(L"="); auto exeIter = g_aliasData.find(exeNameString); if (exeIter != g_aliasData.end()) { - auto list = exeIter->second; + const auto& list = exeIter->second; for (auto& pair : list) { // Alias stores lengths in bytes. @@ -464,7 +468,7 @@ void Alias::s_ClearCmdExeAliases() auto exeIter = g_aliasData.find(exeNameString); if (exeIter != g_aliasData.end()) { - auto list = exeIter->second; + const auto& list = exeIter->second; for (auto& pair : list) { // Alias stores lengths in bytes. @@ -818,363 +822,154 @@ void Alias::s_ClearCmdExeAliases() } // Routine Description: -// - Tokenizes a string into a collection using space as a separator +// - Takes the source text and searches it for an alias belonging to exe name's list. // Arguments: -// - str - String to tokenize +// - sourceText - The string to search for an alias +// - exeName - The name of the EXE that has aliases associated +// - lineCount - Number of lines worth of text processed. // Return Value: -// - Collection of tokenized strings -std::deque Alias::s_Tokenize(const std::wstring_view str) +// - If we found a matching alias, this will be the processed data +// and lineCount is updated to the new number of lines. +// - If we didn't match and process an alias, return an empty string. +std::wstring Alias::s_MatchAndCopyAlias(std::wstring_view sourceText, std::wstring_view exeName, size_t& lineCount) { - std::deque result; - - size_t prevIndex = 0; - auto spaceIndex = str.find(L' '); - while (std::wstring_view::npos != spaceIndex) + // Check if we have an EXE in the list that matches the request first. + const auto exeIter = g_aliasData.find(exeName); + if (exeIter == g_aliasData.end()) { - const auto length = spaceIndex - prevIndex; - - result.emplace_back(str.substr(prevIndex, length)); - - spaceIndex++; - prevIndex = spaceIndex; - - spaceIndex = str.find(L' ', spaceIndex); + // We found no data for this exe. + return {}; } - // Place the final one into the set. - result.emplace_back(str.substr(prevIndex)); - - return result; -} - -// Routine Description: -// - Gets just the arguments portion of the command string -// Specifically, all text after the first space character. -// Arguments: -// - str - String to split into just args -// Return Value: -// - Only the arguments part of the string or empty if there are no arguments. -std::wstring Alias::s_GetArgString(const std::wstring_view str) -{ - std::wstring result; - auto firstSpace = str.find_first_of(L' '); - if (std::wstring_view::npos != firstSpace) + const auto& exeList = exeIter->second; + if (exeList.size() == 0) { - firstSpace++; - if (firstSpace < str.size()) - { - result = str.substr(firstSpace); - } + return {}; } - return result; -} + std::wstring_view args[10]; + size_t argc = 0; -// Routine Description: -// - Checks the given character to see if it is a numbered arg replacement macro -// and replaces it with the counted argument if there is a match -// Arguments: -// - ch - Character to test as a macro -// - appendToStr - Append the macro result here if it matched -// - tokens - Tokens of the original command string. 0 is alias. 1-N are arguments. -// Return Value: -// - True if we found the macro and appended to the string. -// - False if the given character doesn't match this macro. -bool Alias::s_TryReplaceNumberedArgMacro(const wchar_t ch, - std::wstring& appendToStr, - const std::deque& tokens) -{ - if (ch >= L'1' && ch <= L'9') + // Split the source string into whitespace delimited arguments. + for (size_t argBegIdx = 0; argBegIdx < sourceText.size();) { - // Numerical macros substitute that numbered argument - const size_t index = ch - L'0'; + // Find the end of the current word (= argument). + const auto argEndIdx = sourceText.find_first_of(L' ', argBegIdx); + const auto str = til::safe_slice_abs(sourceText, argBegIdx, argEndIdx); - if (index < tokens.size() && index > 0) + // str is empty if the text starting at argBegIdx is whitespace. + // This can only occur if either the source text starts with whitespace or past + // an argument there's only whitespace text left until the end of sourceText. + if (str.empty()) { - appendToStr.append(tokens[index]); + break; } - return true; - } + args[argc] = str; + argc++; - return false; -} + if (argc >= std::size(args)) + { + break; + } -// Routine Description: -// - Checks the given character to see if it is a wildcard arg replacement macro -// and replaces it with the entire argument string if there is a match -// Arguments: -// - ch - Character to test as a macro -// - appendToStr - Append the macro result here if it matched -// - fullArgString - All of the arguments as one big string. -// Return Value: -// - True if we found the macro and appended to the string. -// - False if the given character doesn't match this macro. -bool Alias::s_TryReplaceWildcardArgMacro(const wchar_t ch, - std::wstring& appendToStr, - const std::wstring fullArgString) -{ - if (L'*' == ch) - { - // Wildcard substitutes all arguments - appendToStr.append(fullArgString); - return true; + // Find the start of the next word (= argument). + // If the rest of the text is only whitespace, argBegIdx will be npos + // and the for() loop condition will make us exit. + argBegIdx = sourceText.find_first_not_of(L' ', argEndIdx); } - return false; -} - -// Routine Description: -// - Checks the given character to see if it is an input redirection macro -// and replaces it with the < redirector if there is a match -// Arguments: -// - ch - Character to test as a macro -// - appendToStr - Append the macro result here if it matched -// Return Value: -// - True if we found the macro and appended to the string. -// - False if the given character doesn't match this macro. -bool Alias::s_TryReplaceInputRedirMacro(const wchar_t ch, - std::wstring& appendToStr) -{ - if (L'L' == towupper(ch)) + // As mentioned above, argc will be 0 if the source text starts with whitespace or only consists of whitespace. + if (argc == 0) { - // L (either case) replaces with input redirector < - appendToStr.push_back(L'<'); - return true; + return {}; } - return false; -} -// Routine Description: -// - Checks the given character to see if it is an output redirection macro -// and replaces it with the > redirector if there is a match -// Arguments: -// - ch - Character to test as a macro -// - appendToStr - Append the macro result here if it matched -// Return Value: -// - True if we found the macro and appended to the string. -// - False if the given character doesn't match this macro. -bool Alias::s_TryReplaceOutputRedirMacro(const wchar_t ch, - std::wstring& appendToStr) -{ - if (L'G' == towupper(ch)) + // The text up to the first space is the alias name. + const auto aliasIter = exeList.find(args[0]); + if (aliasIter == exeList.end()) { - // G (either case) replaces with output redirector > - appendToStr.push_back(L'>'); - return true; + return {}; } - return false; -} -// Routine Description: -// - Checks the given character to see if it is a pipe redirection macro -// and replaces it with the | redirector if there is a match -// Arguments: -// - ch - Character to test as a macro -// - appendToStr - Append the macro result here if it matched -// Return Value: -// - True if we found the macro and appended to the string. -// - False if the given character doesn't match this macro. -bool Alias::s_TryReplacePipeRedirMacro(const wchar_t ch, - std::wstring& appendToStr) -{ - if (L'B' == towupper(ch)) + const auto& target = aliasIter->second; + if (target.size() == 0) { - // B (either case) replaces with pipe operator | - appendToStr.push_back(L'|'); - return true; + return {}; } - return false; -} -// Routine Description: -// - Checks the given character to see if it is a next command macro -// and replaces it with CRLF if there is a match -// Arguments: -// - ch - Character to test as a macro -// - appendToStr - Append the macro result here if it matched -// - lineCount - Updates the rolling count of lines if we add a CRLF. -// Return Value: -// - True if we found the macro and appended to the string. -// - False if the given character doesn't match this macro. -bool Alias::s_TryReplaceNextCommandMacro(const wchar_t ch, - std::wstring& appendToStr, - size_t& lineCount) -{ - if (L'T' == towupper(ch)) - { - // T (either case) inserts a CRLF to chain commands - s_AppendCrLf(appendToStr, lineCount); - return true; - } - return false; -} + std::wstring buffer; + size_t lines = 0; -// Routine Description: -// - Appends the system line feed (CRLF) to the given string -// Arguments: -// - appendToStr - Append the system line feed here -// - lineCount - Updates the rolling count of lines if we add a CRLF. -void Alias::s_AppendCrLf(std::wstring& appendToStr, - size_t& lineCount) -{ - appendToStr.push_back(L'\r'); - appendToStr.push_back(L'\n'); - lineCount++; -} - -// Routine Description: -// - Searches through the given string for macros and replaces them -// with the matching action -// Arguments: -// - str - On input, the string to search. On output, the string is replaced. -// - tokens - The tokenized command line input. 0 is the alias, 1-N are arguments. -// - fullArgString - Shorthand to 1-N argument string in case of wildcard match. -// Return Value: -// - The number of commands in the final string (line feeds, CRLFs) -size_t Alias::s_ReplaceMacros(std::wstring& str, - const std::deque& tokens, - const std::wstring& fullArgString) -{ - size_t lineCount = 0; - std::wstring finalText; - - // The target text may contain substitution macros indicated by $. - // Walk through and substitute them as appropriate. - for (auto ch = str.cbegin(); ch < str.cend(); ch++) + for (auto it = target.begin(), end = target.end(); it != end;) { - if (L'$' == *ch) + auto ch = *it++; + if (ch != L'$' || it == end) { - // Attempt to read ahead by one character. - const auto chNext = ch + 1; + buffer.push_back(ch); + continue; + } - if (chNext < str.cend()) + // $ is our "escape character" and this code handles the escape + // sequence consisting of a single subsequent character. + ch = *it++; + const auto chLower = til::tolower_ascii(ch); + if (chLower >= L'1' && chLower <= L'9') + { + // $1-9 = append the given parameter + const size_t idx = chLower - L'0'; + if (idx < argc) { - auto isProcessed = s_TryReplaceNumberedArgMacro(*chNext, finalText, tokens); - if (!isProcessed) - { - isProcessed = s_TryReplaceWildcardArgMacro(*chNext, finalText, fullArgString); - } - if (!isProcessed) - { - isProcessed = s_TryReplaceInputRedirMacro(*chNext, finalText); - } - if (!isProcessed) - { - isProcessed = s_TryReplaceOutputRedirMacro(*chNext, finalText); - } - if (!isProcessed) - { - isProcessed = s_TryReplacePipeRedirMacro(*chNext, finalText); - } - if (!isProcessed) - { - isProcessed = s_TryReplaceNextCommandMacro(*chNext, finalText, lineCount); - } - if (!isProcessed) - { - // If nothing matches, just push these two characters in. - finalText.push_back(*ch); - finalText.push_back(*chNext); - } - - // Since we read ahead and used that character, - // advance the iterator one extra to compensate. - ch++; + buffer.append(args[idx]); } - else + } + else if (chLower == L'*') + { + // $* = append all parameters + if (argc > 1) { - // If no read-ahead, just push this character and be done. - finalText.push_back(*ch); + // args[] is an array of slices into the source text. This appends the text + // starting at first argument up to the end of the source to the buffer. + buffer.append(args[1].data(), sourceText.data() + sourceText.size()); } } + else if (chLower == L'l') + { + buffer.push_back(L'<'); + } + else if (chLower == L'g') + { + buffer.push_back(L'>'); + } + else if (chLower == L'b') + { + buffer.push_back(L'|'); + } + else if (chLower == L't') + { + buffer.append(L"\r\n"); + lines++; + } else { - // If it didn't match the macro specifier $, push the character. - finalText.push_back(*ch); + buffer.push_back(L'$'); + buffer.push_back(ch); } } - // We always terminate with a CRLF to symbolize end of command. - s_AppendCrLf(finalText, lineCount); + buffer.append(L"\r\n"); + lines++; - // Give back the final text and count. - str.swap(finalText); - return lineCount; + lineCount = lines; + return buffer; } -// Routine Description: -// - Takes the source text and searches it for an alias belonging to exe name's list. -// Arguments: -// - sourceText - The string to search for an alias -// - exeName - The name of the EXE that has aliases associated -// - lineCount - Number of lines worth of text processed. -// Return Value: -// - If we found a matching alias, this will be the processed data -// and lineCount is updated to the new number of lines. -// - If we didn't match and process an alias, return an empty string. -std::wstring Alias::s_MatchAndCopyAlias(std::wstring_view sourceText, const std::wstring& exeName, size_t& lineCount) -{ - // Check if we have an EXE in the list that matches the request first. - auto exeIter = g_aliasData.find(exeName); - if (exeIter == g_aliasData.end()) - { - // We found no data for this exe. Give back an empty string. - return std::wstring(); - } - - auto exeList = exeIter->second; - if (exeList.size() == 0) - { - // If there's no match, give back an empty string. - return std::wstring(); - } - - // Tokenize the text by spaces - const auto tokens = s_Tokenize(sourceText); - - // If there are no tokens, return an empty string - if (tokens.size() == 0) - { - return std::wstring(); - } - - // Find alias. If there isn't one, return an empty string - const auto alias = tokens.front(); - const auto aliasIter = exeList.find(alias); - if (aliasIter == exeList.end()) - { - // We found no alias pair with this name. Give back an empty string. - return std::wstring(); - } - - const auto& target = aliasIter->second; - if (target.size() == 0) - { - return std::wstring(); - } - - // Get the string of all parameters as a shorthand for $* later. - const auto allParams = s_GetArgString(sourceText); - - // The final text will be the target but with macros replaced. - auto finalText = target; - lineCount = s_ReplaceMacros(finalText, tokens, allParams); - - return finalText; -} - -#ifdef UNIT_TESTING -void Alias::s_TestAddAlias(std::wstring& exe, - std::wstring& alias, - std::wstring& target) +void Alias::s_TestAddAlias(std::wstring exe, std::wstring alias, std::wstring target) { - g_aliasData[exe][alias] = target; + g_aliasData[std::move(exe)][std::move(alias)] = std::move(target); } void Alias::s_TestClearAliases() { g_aliasData.clear(); } - -#endif diff --git a/src/host/alias.h b/src/host/alias.h index 9c8bc38d534..77d6abbd0ec 100644 --- a/src/host/alias.h +++ b/src/host/alias.h @@ -16,43 +16,8 @@ class Alias public: static void s_ClearCmdExeAliases(); - static std::wstring s_MatchAndCopyAlias(std::wstring_view sourceText, const std::wstring& exeName, size_t& lineCount); - -private: - static std::deque s_Tokenize(const std::wstring_view str); - static std::wstring s_GetArgString(const std::wstring_view str); - static size_t s_ReplaceMacros(std::wstring& str, - const std::deque& tokens, - const std::wstring& fullArgString); - - static bool s_TryReplaceNumberedArgMacro(const wchar_t ch, - std::wstring& appendToStr, - const std::deque& tokens); - static bool s_TryReplaceWildcardArgMacro(const wchar_t ch, - std::wstring& appendToStr, - const std::wstring fullArgString); - - static bool s_TryReplaceInputRedirMacro(const wchar_t ch, - std::wstring& appendToStr); - static bool s_TryReplaceOutputRedirMacro(const wchar_t ch, - std::wstring& appendToStr); - static bool s_TryReplacePipeRedirMacro(const wchar_t ch, - std::wstring& appendToStr); - - static bool s_TryReplaceNextCommandMacro(const wchar_t ch, - std::wstring& appendToStr, - size_t& lineCount); - - static void s_AppendCrLf(std::wstring& appendToStr, - size_t& lineCount); - -#ifdef UNIT_TESTING - static void s_TestAddAlias(std::wstring& exe, - std::wstring& alias, - std::wstring& target); + static std::wstring s_MatchAndCopyAlias(std::wstring_view sourceText, std::wstring_view exeName, size_t& lineCount); + static void s_TestAddAlias(std::wstring exe, std::wstring alias, std::wstring target); static void s_TestClearAliases(); - - friend class AliasTests; -#endif }; diff --git a/src/host/ut_host/AliasTests.cpp b/src/host/ut_host/AliasTests.cpp index cf499bea89e..39c4bf559e1 100644 --- a/src/host/ut_host/AliasTests.cpp +++ b/src/host/ut_host/AliasTests.cpp @@ -3,7 +3,6 @@ #include "precomp.h" #include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" #include "alias.h" @@ -63,7 +62,7 @@ class AliasTests BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Data:exeName", L"{test.exe}") TEST_METHOD_PROPERTY(L"Data:aliasName", L"{foo}") - TEST_METHOD_PROPERTY(L"Data:originalString", L"{ foo one two three four five six seven eight nine ten eleven twelve }") + TEST_METHOD_PROPERTY(L"Data:originalString", L"{ foo one two three four five six seven eight nine ten eleven twelve }") TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", L"{" // Each of these is a human-generated test of macro before and after. L"bar=bar%," // The character % will be turned into an \r\n @@ -77,7 +76,7 @@ class AliasTests L"bar $8=bar eight%," L"bar $9=bar nine%," L"bar $3 $1 $4 $1 $5 $9=bar three one four one five nine%," // assorted mixed order parameters with a repeat - L"bar $*=bar one two three four five six seven eight nine ten eleven twelve%," + L"bar $*=bar one two three four five six seven eight nine ten eleven twelve%," L"longer=longer%," // replace with a target longer than the original alias L"redirect $1$goutput $2=redirect one>output two%," // doing these without spaces between some commands L"REDIRECT $1$GOUTPUT $2=REDIRECT one>OUTPUT two%," // also notice we're checking both upper and lowercase @@ -91,7 +90,7 @@ class AliasTests L"MyMoney$$$$$$App=MyMoney$$$$$$App%," // this is a long standing bug, $$ isn't replaced with $. L"Invalid$Apple=Invalid$Apple%," // An invalid macro $A is copied through L"IEndInA$=IEndInA$%," // Ending in a $ is copied through. - L"megamix $7$Gfun $1 $b test $9 $L $2.txt$tall$$the$$things $*$tat$g$gonce.log=megamix seven>fun one | test nine < two.txt%all$$the$$things one two three four five six seven eight nine ten eleven twelve%at>>once.log%" + L"megamix $7$Gfun $1 $b test $9 $L $2.txt$tall$$the$$things $*$tat$g$gonce.log=megamix seven>fun one | test nine < two.txt%all$$the$$things one two three four five six seven eight nine ten eleven twelve%at>>once.log%" L"}") END_TEST_METHOD_PROPERTIES() @@ -181,258 +180,4 @@ class AliasTests VERIFY_IS_TRUE(buffer.empty()); VERIFY_ARE_EQUAL(1u, dwLines); } - - TEST_METHOD(Tokenize) - { - std::wstring tokenStr(L"one two three"); - std::deque tokensExpected; - tokensExpected.emplace_back(L"one"); - tokensExpected.emplace_back(L"two"); - tokensExpected.emplace_back(L"three"); - - auto tokensActual = Alias::s_Tokenize(tokenStr); - - VERIFY_ARE_EQUAL(tokensExpected.size(), tokensActual.size()); - - for (size_t i = 0; i < tokensExpected.size(); i++) - { - VERIFY_ARE_EQUAL(String(tokensExpected[i].data()), String(tokensActual[i].data())); - } - } - - TEST_METHOD(TokenizeNothing) - { - std::wstring tokenStr(L"alias"); - std::deque tokensExpected; - tokensExpected.emplace_back(tokenStr); - - auto tokensActual = Alias::s_Tokenize(tokenStr); - - VERIFY_ARE_EQUAL(tokensExpected.size(), tokensActual.size()); - - for (size_t i = 0; i < tokensExpected.size(); i++) - { - VERIFY_ARE_EQUAL(String(tokensExpected[i].data()), String(tokensActual[i].data())); - } - } - - TEST_METHOD(GetArgString) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", - L"{" - L"alias arg1 arg2 arg3=arg1 arg2 arg3," - L"aliasOnly=" - L"}") - END_TEST_METHOD_PROPERTIES() - - std::wstring target; - std::wstring expected; - _RetrieveTargetExpectedPair(target, expected); - - auto actual = Alias::s_GetArgString(target); - - VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); - } - - TEST_METHOD(NumberedArgMacro) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", - L"{" - L"1=one," - L"2=two," - L"3=three," - L"4=four," - L"5=five," - L"6=six," - L"7=seven," - L"8=eight," - L"9=nine," - L"A=," - L"0=," - L"}") - END_TEST_METHOD_PROPERTIES() - - std::wstring target; - std::wstring expected; - _RetrieveTargetExpectedPair(target, expected); - - std::deque tokens; - tokens.emplace_back(L"alias"); - tokens.emplace_back(L"one"); - tokens.emplace_back(L"two"); - tokens.emplace_back(L"three"); - tokens.emplace_back(L"four"); - tokens.emplace_back(L"five"); - tokens.emplace_back(L"six"); - tokens.emplace_back(L"seven"); - tokens.emplace_back(L"eight"); - tokens.emplace_back(L"nine"); - tokens.emplace_back(L"ten"); - - // if we expect non-empty results, then we should get a bool back saying it was processed - const auto returnExpected = !expected.empty(); - - std::wstring actual; - const auto returnActual = Alias::s_TryReplaceNumberedArgMacro(target[0], actual, tokens); - - VERIFY_ARE_EQUAL(returnExpected, returnActual); - VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); - } - - TEST_METHOD(WildcardArgMacro) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", - L"{" - L"*=one two three," - L"A=," - L"0=," - L"}") - END_TEST_METHOD_PROPERTIES() - - std::wstring target; - std::wstring expected; - _RetrieveTargetExpectedPair(target, expected); - - std::wstring fullArgString(L"one two three"); - - // if we expect non-empty results, then we should get a bool back saying it was processed - const auto returnExpected = !expected.empty(); - - std::wstring actual; - const auto returnActual = Alias::s_TryReplaceWildcardArgMacro(target[0], actual, fullArgString); - - VERIFY_ARE_EQUAL(returnExpected, returnActual); - VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); - } - - TEST_METHOD(InputRedirMacro) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", - L"{" - L"L=<," - L"l=<," - L"A=," - L"a=," - L"0=," - L"}") - END_TEST_METHOD_PROPERTIES() - - std::wstring target; - std::wstring expected; - _RetrieveTargetExpectedPair(target, expected); - - // if we expect non-empty results, then we should get a bool back saying it was processed - const auto returnExpected = !expected.empty(); - - std::wstring actual; - const auto returnActual = Alias::s_TryReplaceInputRedirMacro(target[0], actual); - - VERIFY_ARE_EQUAL(returnExpected, returnActual); - VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); - } - - TEST_METHOD(OutputRedirMacro) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", - L"{" - L"G=>," - L"g=>," - L"A=," - L"a=," - L"0=," - L"}") - END_TEST_METHOD_PROPERTIES() - - std::wstring target; - std::wstring expected; - _RetrieveTargetExpectedPair(target, expected); - - // if we expect non-empty results, then we should get a bool back saying it was processed - const auto returnExpected = !expected.empty(); - - std::wstring actual; - const auto returnActual = Alias::s_TryReplaceOutputRedirMacro(target[0], actual); - - VERIFY_ARE_EQUAL(returnExpected, returnActual); - VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); - } - - TEST_METHOD(PipeRedirMacro) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", - L"{" - L"B=|," - L"b=|," - L"A=," - L"a=," - L"0=," - L"}") - END_TEST_METHOD_PROPERTIES() - - std::wstring target; - std::wstring expected; - _RetrieveTargetExpectedPair(target, expected); - - // if we expect non-empty results, then we should get a bool back saying it was processed - const auto returnExpected = !expected.empty(); - - std::wstring actual; - const auto returnActual = Alias::s_TryReplacePipeRedirMacro(target[0], actual); - - VERIFY_ARE_EQUAL(returnExpected, returnActual); - VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); - } - - TEST_METHOD(NextCommandMacro) - { - BEGIN_TEST_METHOD_PROPERTIES() - TEST_METHOD_PROPERTY(L"Data:targetExpectedPair", - L"{" - L"T=%," - L"t=%," - L"A=," - L"a=," - L"0=," - L"}") - END_TEST_METHOD_PROPERTIES() - - std::wstring target; - std::wstring expected; - _RetrieveTargetExpectedPair(target, expected); - - _ReplacePercentWithCRLF(expected); - - // if we expect non-empty results, then we should get a bool back saying it was processed - const auto returnExpected = !expected.empty(); - - std::wstring actual; - size_t lineCountActual = 0; - - const auto lineCountExpected = lineCountActual + (returnExpected ? 1 : 0); - - const auto returnActual = Alias::s_TryReplaceNextCommandMacro(target[0], actual, lineCountActual); - - VERIFY_ARE_EQUAL(returnExpected, returnActual); - VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); - VERIFY_ARE_EQUAL(lineCountExpected, lineCountActual); - } - - TEST_METHOD(AppendCrLf) - { - std::wstring actual; - size_t lineCountActual = 0; - - const std::wstring expected(L"\r\n"); - const auto lineCountExpected = lineCountActual + 1; - - Alias::s_AppendCrLf(actual, lineCountActual); - VERIFY_ARE_EQUAL(String(expected.data()), String(actual.data())); - VERIFY_ARE_EQUAL(lineCountExpected, lineCountActual); - } }; diff --git a/src/inc/til.h b/src/inc/til.h index c1d799fe076..eb781634fe2 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -55,7 +55,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { template - as_view_t clamp_slice_abs(const T& view, size_t beg, size_t end) + as_view_t safe_slice_abs(const T& view, size_t beg, size_t end) { const auto len = view.size(); end = std::min(end, len); @@ -64,7 +64,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } template - as_view_t clamp_slice_len(const T& view, size_t start, size_t count) + as_view_t safe_slice_len(const T& view, size_t start, size_t count) { const auto len = view.size(); start = std::min(start, len); diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 43b636336ae..e5b3d061f54 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -1524,7 +1524,7 @@ BackendD3D::ShadingType BackendD3D::_drawSoftFontGlyph(const RenderingPayload& p const auto width = static_cast(p.s->font->softFontCellSize.width); const auto height = static_cast(p.s->font->softFontCellSize.height); const auto softFontIndex = glyphIndex - 0xEF20u; - const auto data = til::clamp_slice_len(p.s->font->softFontPattern, height * softFontIndex, height); + const auto data = til::safe_slice_len(p.s->font->softFontPattern, height * softFontIndex, height); // This happens if someone wrote a U+EF2x character (by accident), but we don't even have soft fonts enabled yet. if (data.empty() || data.size() != height)