This commit is contained in:
Leonard Hecker 2024-09-25 20:22:38 +02:00
parent 1daa180b22
commit 0e4f03c35a
11 changed files with 338 additions and 464 deletions

View File

@ -1035,8 +1035,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
if (windowId == 0)
{
// Try the name as an integer ID
uint32_t temp;
if (!Utils::StringToUint(window.c_str(), temp))
const auto temp = til::parse_unsigned<uint32_t>(window.c_str());
if (!temp)
{
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_MoveContent_FailedToParseId",
@ -1045,7 +1045,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
else
{
windowId = temp;
windowId = *temp;
}
}

View File

@ -19,39 +19,17 @@
using Microsoft::Console::Interactivity::ServiceLocator;
struct case_insensitive_hash
struct insensitive_less
{
using is_transparent = void;
using is_transparent = int;
std::size_t operator()(const std::wstring_view& key) const
bool operator()(const std::wstring_view& lhs, const std::wstring_view& rhs) const noexcept
{
til::hasher h;
for (const auto& ch : key)
{
h.write(::towlower(ch));
}
return h.finalize();
return til::compare_ordinal_insensitive(lhs, rhs) < 0;
}
};
struct case_insensitive_equality
{
using is_transparent = void;
bool operator()(const std::wstring_view& lhs, const std::wstring_view& rhs) const
{
return til::compare_ordinal_insensitive(lhs, rhs) == 0;
}
};
std::unordered_map<std::wstring,
std::unordered_map<std::wstring,
std::wstring,
case_insensitive_hash,
case_insensitive_equality>,
case_insensitive_hash,
case_insensitive_equality>
g_aliasData;
static std::map<std::wstring, std::map<std::wstring, std::wstring, insensitive_less>, insensitive_less> g_aliasData;
// Routine Description:
// - Adds a command line alias to the global set.
@ -99,26 +77,26 @@ std::unordered_map<std::wstring,
try
{
std::wstring exeNameString(exeName);
std::wstring sourceString(source);
std::wstring targetString(target);
std::transform(exeNameString.begin(), exeNameString.end(), exeNameString.begin(), towlower);
std::transform(sourceString.begin(), sourceString.end(), sourceString.begin(), towlower);
if (targetString.size() == 0)
if (target.empty())
{
// Only try to dig in and erase if the exeName exists.
auto exeData = g_aliasData.find(exeNameString);
const auto exeData = g_aliasData.find(exeName);
if (exeData != g_aliasData.end())
{
g_aliasData[exeNameString].erase(sourceString);
// With C++23 this can be replaced with just a single erase() call.
const auto sourceData = exeData->second.find(source);
if (sourceData != exeData->second.end())
{
exeData->second.erase(sourceData);
}
}
}
else
{
// Map will auto-create each level as necessary
g_aliasData[exeNameString][sourceString] = targetString;
// With C++26 this can written a lot shorter with operator[].
const auto exeData = g_aliasData.emplace(std::piecewise_construct, std::forward_as_tuple(exeName), std::forward_as_tuple()).first;
const auto sourceData = exeData->second.emplace(std::piecewise_construct, std::forward_as_tuple(source), std::forward_as_tuple()).first;
sourceData->second.assign(exeName);
}
}
CATCH_RETURN();
@ -153,18 +131,18 @@ std::unordered_map<std::wstring,
til::at(*target, 0) = UNICODE_NULL;
}
std::wstring exeNameString(exeName);
std::wstring sourceString(source);
// For compatibility, return ERROR_GEN_FAILURE for any result where the alias can't be found.
// We use .find for the iterators then dereference to search without creating entries.
const auto exeIter = g_aliasData.find(exeNameString);
// In the past a not-found resulted in a STATUS_UNSUCCESSFUL.
// This was then translated by ntdll into ERROR_GEN_FAILURE.
// Since we stopped using NTSTATUS codes, we now return ERROR_GEN_FAILURE directly.
const auto exeIter = g_aliasData.find(exeName);
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), exeIter == g_aliasData.end());
const auto& exeData = exeIter->second;
const auto sourceIter = exeData.find(sourceString);
const auto sourceIter = exeData.find(source);
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), sourceIter == exeData.end());
const auto& targetString = sourceIter->second;
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), targetString.size() == 0);
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_GEN_FAILURE), targetString.empty());
// TargetLength is a byte count, convert to characters.
auto targetSize = targetString.size();
@ -337,8 +315,7 @@ static std::wstring aliasesSeparator(L"=");
auto exeIter = g_aliasData.find(exeNameString);
if (exeIter != g_aliasData.end())
{
const auto& list = exeIter->second;
for (auto& pair : list)
for (auto& pair : exeIter->second)
{
// Alias stores lengths in bytes.
auto cchSource = pair.first.size();
@ -422,7 +399,7 @@ static std::wstring aliasesSeparator(L"=");
void Alias::s_ClearCmdExeAliases()
{
// find without creating.
auto exeIter = g_aliasData.find(L"cmd.exe");
const auto exeIter = g_aliasData.find(L"cmd.exe");
if (exeIter != g_aliasData.end())
{
exeIter->second.clear();
@ -465,11 +442,10 @@ void Alias::s_ClearCmdExeAliases()
const size_t cchNull = 1;
// Find without creating.
auto exeIter = g_aliasData.find(exeNameString);
const auto exeIter = g_aliasData.find(exeNameString);
if (exeIter != g_aliasData.end())
{
const auto& list = exeIter->second;
for (auto& pair : list)
for (auto& pair : exeIter->second)
{
// Alias stores lengths in bytes.
const auto cchSource = pair.first.size();

View File

@ -757,38 +757,21 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
// GH#1222 and GH#9754 - Use the "virtual" viewport here, so that the
// viewport snaps back to the virtual viewport's location.
const auto currentViewport = buffer.GetVirtualViewport().ToInclusive();
til::point delta;
{
// When evaluating the X offset, we must convert the buffer position to
// equivalent screen coordinates, taking line rendition into account.
const auto lineRendition = buffer.GetTextBuffer().GetLineRendition(position.y);
const auto screenPosition = BufferToScreenLine(til::inclusive_rect{ position.x, position.y, position.x, position.y }, lineRendition);
if (currentViewport.left > screenPosition.left)
{
delta.x = screenPosition.left - currentViewport.left;
}
else if (currentViewport.right < screenPosition.right)
{
delta.x = screenPosition.right - currentViewport.right;
}
// When evaluating the X offset, we must convert the buffer position to
// equivalent screen coordinates, taking line rendition into account.
const auto lineRendition = buffer.GetTextBuffer().GetLineRendition(position.y);
const auto screenPosition = BufferToScreenLine(til::inclusive_rect{ position.x, position.y, position.x, position.y }, lineRendition);
if (currentViewport.top > position.y)
{
delta.y = position.y - currentViewport.top;
}
else if (currentViewport.bottom < position.y)
{
delta.y = position.y - currentViewport.bottom;
}
}
auto left = std::min(currentViewport.left, screenPosition.left);
left = std::max(left, screenPosition.right - currentViewport.right + currentViewport.left);
auto top = std::min(currentViewport.top, screenPosition.top);
top = std::max(top, screenPosition.bottom - currentViewport.bottom + currentViewport.top);
til::point newWindowOrigin;
newWindowOrigin.x = currentViewport.left + delta.x;
newWindowOrigin.y = currentViewport.top + delta.y;
// SetViewportOrigin will worry about clamping these values to the
// buffer for us.
RETURN_IF_NTSTATUS_FAILED(buffer.SetViewportOrigin(true, newWindowOrigin, true));
RETURN_IF_NTSTATUS_FAILED(buffer.SetViewportOrigin(true, { left, top }, true));
// SetViewportOrigin will only move the virtual bottom down, but in
// this particular case we also need to allow the virtual bottom to

127
src/inc/til/point_span.h Normal file
View File

@ -0,0 +1,127 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
// point_span can be pictured as a "selection" range inside our text buffer. So given
// a text buffer of 10x4, a start of 4,1 and end of 7,3 the span might look like this:
// +----------+
// | |
// | xxxxxx|
// |xxxxxxxxxx|
// |xxxxxxxx |
// +----------+
// At the time of writing there's a push to make selections have an exclusive end coordinate,
// so the interpretation of end might change soon (making this comment potentially outdated).
struct point_span
{
til::point start;
til::point end;
constexpr bool operator==(const point_span& rhs) const noexcept
{
// `__builtin_memcmp` isn't an official standard, but it's the
// only way at the time of writing to get a constexpr `memcmp`.
return __builtin_memcmp(this, &rhs, sizeof(rhs)) == 0;
}
constexpr bool operator!=(const point_span& rhs) const noexcept
{
return __builtin_memcmp(this, &rhs, sizeof(rhs)) != 0;
}
// Calls func(row, begX, endX) for each row and begX and begY are inclusive coordinates,
// because point_span itself also uses inclusive coordinates.
// In other words, it turns a
// ```
// +----------------+
// | #########|
// |################|
// |#### |
// +----------------+
// ```
// into:
// ```
// func(0, 8, 15)
// func(1, 0, 15)
// func(2, 0, 4)
// ```
constexpr void iterate_rows(til::CoordType width, auto&& func) const
{
// Copy the members so that the compiler knows it doesn't
// need to re-read them on every loop iteration.
const auto w = width - 1;
const auto ax = std::clamp(start.x, 0, w);
const auto ay = start.y;
const auto bx = std::clamp(end.x, 0, w);
const auto by = end.y;
for (auto y = ay; y <= by; ++y)
{
const auto x1 = y != ay ? 0 : ax;
const auto x2 = y != by ? w : bx;
func(y, x1, x2);
}
}
til::small_vector<til::inclusive_rect, 3> split_rects(til::CoordType width) const
{
// Copy the members so that the compiler knows it doesn't
// need to re-read them on every loop iteration.
const auto w = width - 1;
const auto ax = std::clamp(start.x, 0, w);
const auto ay = start.y;
const auto bx = std::clamp(end.x, 0, w);
const auto by = end.y;
auto y = ay;
til::small_vector<til::inclusive_rect, 3> rects;
// +----------------+
// | #########| <-- A
// |################| <-- B
// |#### | <-- C
// +----------------+
//
// +----------------+
// |################| <-- B
// |################| <-- B
// |#### | <-- C
// +----------------+
//
// +----------------+
// | #########| <-- A
// |################| <-- C
// |################| <-- C
// +----------------+
//
// +----------------+
// |################| <-- C
// |################| <-- C
// |################| <-- C
// +----------------+
if (y <= by && ax > 0) // A
{
const auto x2 = y == by ? bx : w;
rects.emplace_back(ax, y, x2, y);
y += 1;
}
if (y < by && bx < w) // B
{
rects.emplace_back(0, y, w, by - 1);
y = by - 1;
}
if (y <= by) // C
{
rects.emplace_back(0, y, bx, by);
}
return rects;
}
};
}

View File

@ -3448,36 +3448,42 @@ void AdaptDispatch::DoConEmuAction(const std::wstring_view string)
constexpr size_t TaskbarMaxState{ 4 };
constexpr size_t TaskbarMaxProgress{ 100 };
unsigned int state = 0;
unsigned int progress = 0;
const auto parts = Utils::SplitString(string, L';');
unsigned int subParam = 0;
if (parts.size() < 1)
{
return;
}
if (parts.size() < 1 || !Utils::StringToUint(til::at(parts, 0), subParam))
const auto subParam = til::parse_unsigned<unsigned int>(til::at(parts, 0));
if (!subParam)
{
return;
}
// 4 is SetProgressBar, which sets the taskbar state/progress.
if (subParam == 4)
if (*subParam == 4)
{
if (parts.size() >= 2)
unsigned int state = 0;
if (parts.size() >= 2 && !til::at(parts, 1).empty())
{
// A state parameter is defined, parse it out
const auto stateSuccess = Utils::StringToUint(til::at(parts, 1), state);
if (!stateSuccess && !til::at(parts, 1).empty())
const auto stateOpt = til::parse_unsigned<unsigned int>(til::at(parts, 1));
state = stateOpt.value_or(0);
if (!stateOpt)
{
return;
}
if (parts.size() >= 3)
}
unsigned int progress = 0;
if (parts.size() >= 3 && !til::at(parts, 2).empty())
{
// A progress parameter is also defined, parse it out
const auto progressOpt = til::parse_unsigned<unsigned int>(til::at(parts, 2));
progress = progressOpt.value_or(0);
if (!progressOpt)
{
// A progress parameter is also defined, parse it out
const auto progressSuccess = Utils::StringToUint(til::at(parts, 2), progress);
if (!progressSuccess && !til::at(parts, 2).empty())
{
return;
}
return;
}
}
@ -3494,7 +3500,7 @@ void AdaptDispatch::DoConEmuAction(const std::wstring_view string)
_api.SetTaskbarProgress(static_cast<DispatchTypes::TaskbarState>(state), progress);
}
// 9 is SetWorkingDirectory, which informs the terminal about the current working directory.
else if (subParam == 9)
else if (*subParam == 9)
{
if (parts.size() >= 2)
{
@ -3523,7 +3529,7 @@ void AdaptDispatch::DoConEmuAction(const std::wstring_view string)
// * https://conemu.github.io/en/ShellWorkDir.html#PowerShell
//
// This seems like basically the same as 133;B - the end of the prompt, the start of the commandline.
else if (subParam == 12)
else if (*subParam == 12)
{
_pages.ActivePage().Buffer().StartCommand();
}
@ -3613,9 +3619,7 @@ void AdaptDispatch::DoFinalTermAction(const std::wstring_view string)
// error and move on.
//
// We know that "0" will be successfully parsed, and that's close enough.
unsigned int parsedError = 0;
error = Utils::StringToUint(errorString, parsedError) ? parsedError :
UINT_MAX;
error = til::parse_unsigned<unsigned int>(errorString).value_or(UINT_MAX);
}
_pages.ActivePage().Buffer().EndCurrentCommand(error);
@ -3667,16 +3671,26 @@ void AdaptDispatch::DoVsCodeAction(const std::wstring_view string)
// 2: $($completions.ReplacementLength);
// 3: $($cursorIndex);
// 4: $completions.CompletionMatches | ConvertTo-Json
unsigned int replacementIndex = 0;
unsigned int replacementLength = 0;
unsigned int cursorIndex = 0;
std::optional<unsigned int> replacementIndex;
std::optional<unsigned int> replacementLength;
std::optional<unsigned int> cursorIndex;
bool succeeded = true;
bool succeeded = (parts.size() >= 2) &&
(Utils::StringToUint(til::at(parts, 1), replacementIndex));
succeeded &= (parts.size() >= 3) &&
(Utils::StringToUint(til::at(parts, 2), replacementLength));
succeeded &= (parts.size() >= 4) &&
(Utils::StringToUint(til::at(parts, 3), cursorIndex));
if (parts.size() >= 2)
{
replacementIndex = til::parse_unsigned<unsigned int>(til::at(parts, 1));
succeeded &= replacementIndex.has_value();
}
if (parts.size() >= 3)
{
replacementLength = til::parse_unsigned<unsigned int>(til::at(parts, 2));
succeeded &= replacementLength.has_value();
}
if (parts.size() >= 4)
{
cursorIndex = til::parse_unsigned<unsigned int>(til::at(parts, 3));
succeeded &= cursorIndex.has_value();
}
// VsCode is using cursorIndex and replacementIndex, but we aren't currently.
if (succeeded)
@ -3695,7 +3709,7 @@ void AdaptDispatch::DoVsCodeAction(const std::wstring_view string)
const auto remainder = string.substr(prefixLength);
_api.InvokeCompletions(parts.size() < 5 ? L"" : remainder,
replacementLength);
replacementLength.value_or(0));
}
// If it's poorly formatted, just eat it

View File

@ -909,19 +909,18 @@ bool OutputStateMachineEngine::_GetOscSetColorTable(const std::wstring_view stri
{
auto&& index = til::at(parts, i);
auto&& color = til::at(parts, j);
unsigned int tableIndex = 0;
const auto indexSuccess = Utils::StringToUint(index, tableIndex);
const auto tableIndex = til::parse_unsigned<unsigned int>(index);
if (indexSuccess)
if (tableIndex)
{
if (color == L"?"sv) [[unlikely]]
{
newTableIndexes.push_back(tableIndex);
newTableIndexes.push_back(*tableIndex);
newRgbs.push_back(COLOR_INQUIRY_COLOR);
}
else if (const auto colorOptional = Utils::ColorFromXTermColor(color))
{
newTableIndexes.push_back(tableIndex);
newTableIndexes.push_back(*tableIndex);
newRgbs.push_back(colorOptional.value());
}
}

View File

@ -417,7 +417,7 @@ static BOOL WINAPI consoleCtrlHandler(DWORD)
int __stdcall main() noexcept
{
g_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
g_stderr = GetStdHandle(STD_OUTPUT_HANDLE);
g_stderr = GetStdHandle(STD_ERROR_HANDLE);
g_console_cp_old = GetConsoleOutputCP();
SetConsoleCtrlHandler(consoleCtrlHandler, TRUE);

View File

@ -289,11 +289,12 @@ constexpr void Utils::InitializeExtendedColorTable(const std::span<COLORREF> tab
// Return Value:
// - An optional color which contains value if a color was successfully parsed
std::optional<til::color> Utils::ColorFromXOrgAppColorName(const std::wstring_view wstr) noexcept
try
{
std::string stem;
char stemBuffer[32];
size_t stemLength = 0;
size_t variantIndex = 0;
auto foundVariant = false;
for (const auto c : wstr)
{
// X11 guarantees that characters are all Latin1.
@ -317,7 +318,7 @@ try
continue;
}
if (foundVariant)
if (foundVariant || stemLength >= std::size(stemBuffer))
{
// Variant should be at the end of the string, e.g., "yellow3".
// This means another non-numeric character is seen, e.g., "yellow3a".
@ -325,9 +326,10 @@ try
return std::nullopt;
}
stem += gsl::narrow_cast<char>(til::tolower_ascii(c));
stemBuffer[stemLength++] = gsl::narrow_cast<char>(til::tolower_ascii(c));
}
const std::string_view stem{ &stemBuffer[0], stemLength };
const auto variantColorIter = xorgAppVariantColorTable.find(stem);
if (variantColorIter != xorgAppVariantColorTable.end())
{
@ -345,7 +347,7 @@ try
{
return std::nullopt;
}
const auto component{ ::base::saturated_cast<uint8_t>(((variantIndex * 255) + 50) / 100) };
const auto component{ gsl::narrow_cast<uint8_t>(((variantIndex * 255) + 50) / 100) };
return til::color{ component, component, component };
}
@ -357,10 +359,5 @@ try
return std::nullopt;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return std::nullopt;
}
#pragma warning(pop)

View File

@ -64,9 +64,7 @@ namespace Microsoft::Console::Utils
til::color ColorFromRGB100(const int r, const int g, const int b) noexcept;
std::tuple<int, int, int> ColorToRGB100(const til::color color) noexcept;
bool HexToUint(const wchar_t wch, unsigned int& value) noexcept;
bool StringToUint(const std::wstring_view wstr, unsigned int& value);
std::vector<std::wstring_view> SplitString(const std::wstring_view wstr, const wchar_t delimiter) noexcept;
til::small_vector<std::wstring_view, 4> SplitString(const std::wstring_view wstr, const wchar_t delimiter) noexcept;
enum FilterOption
{
@ -81,33 +79,6 @@ namespace Microsoft::Console::Utils
std::wstring FilterStringForPaste(const std::wstring_view wstr, const FilterOption option);
constexpr uint16_t EndianSwap(uint16_t value)
{
return (value & 0xFF00) >> 8 |
(value & 0x00FF) << 8;
}
constexpr uint32_t EndianSwap(uint32_t value)
{
return (value & 0xFF000000) >> 24 |
(value & 0x00FF0000) >> 8 |
(value & 0x0000FF00) << 8 |
(value & 0x000000FF) << 24;
}
constexpr unsigned long EndianSwap(unsigned long value)
{
return gsl::narrow_cast<unsigned long>(EndianSwap(gsl::narrow_cast<uint32_t>(value)));
}
constexpr GUID EndianSwap(GUID value)
{
value.Data1 = EndianSwap(value.Data1);
value.Data2 = EndianSwap(value.Data2);
value.Data3 = EndianSwap(value.Data3);
return value;
}
GUID CreateV5Uuid(const GUID& namespaceGuid, const std::span<const std::byte> name);
bool CanUwpDragDrop();

View File

@ -3,11 +3,8 @@
#include "precomp.h"
#include "WexTestClass.h"
#include "../../inc/consoletaeftemplates.hpp"
#include "../inc/utils.hpp"
#include "../inc/colorTable.hpp"
#include <conattrs.hpp>
using namespace WEX::Common;
using namespace WEX::Logging;
@ -23,7 +20,7 @@ class UtilsTests
TEST_METHOD(TestGuidToString);
TEST_METHOD(TestSplitString);
TEST_METHOD(TestFilterStringForPaste);
TEST_METHOD(TestStringToUint);
TEST_METHOD(TestColorFromHexString);
TEST_METHOD(TestColorFromXTermColor);
#if !__INSIDE_WINDOWS
@ -87,7 +84,7 @@ void UtilsTests::TestGuidToString()
void UtilsTests::TestSplitString()
{
std::vector<std::wstring_view> result;
til::small_vector<std::wstring_view, 4> result;
result = SplitString(L"", L';');
VERIFY_ARE_EQUAL(0u, result.size());
result = SplitString(L"1", L';');
@ -201,28 +198,11 @@ void UtilsTests::TestFilterStringForPaste()
FilterStringForPaste(unicodeString, FilterOption::CarriageReturnNewline | FilterOption::ControlCodes));
}
void UtilsTests::TestStringToUint()
void UtilsTests::TestColorFromHexString()
{
auto success = false;
unsigned int value = 0;
success = StringToUint(L"", value);
VERIFY_IS_FALSE(success);
success = StringToUint(L"xyz", value);
VERIFY_IS_FALSE(success);
success = StringToUint(L";", value);
VERIFY_IS_FALSE(success);
success = StringToUint(L"1", value);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(1u, value);
success = StringToUint(L"123", value);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(123u, value);
success = StringToUint(L"123456789", value);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(123456789u, value);
VERIFY_ARE_EQUAL(til::color(0xAA, 0xBB, 0xCC), ColorFromHexString("#abc"));
VERIFY_ARE_EQUAL(til::color(0xAB, 0xCD, 0xEF), ColorFromHexString("#abcdef"));
VERIFY_ARE_EQUAL(til::color(0xAB, 0xCD, 0xEF, 0x01), ColorFromHexString("#abcdef01"));
}
void UtilsTests::TestColorFromXTermColor()

View File

@ -11,17 +11,6 @@
using namespace Microsoft::Console;
// Routine Description:
// - Determines if a character is a valid number character, 0-9.
// Arguments:
// - wch - Character to check.
// Return Value:
// - True if it is. False if it isn't.
static constexpr bool _isNumber(const wchar_t wch) noexcept
{
return wch >= L'0' && wch <= L'9'; // 0x30 - 0x39
}
GSL_SUPPRESS(bounds)
static std::wstring guidToStringCommon(const GUID& guid, size_t offset, size_t length)
{
@ -103,43 +92,34 @@ std::string Utils::ColorToHexString(const til::color color)
// the correct format, throws E_INVALIDARG
til::color Utils::ColorFromHexString(const std::string_view str)
{
THROW_HR_IF(E_INVALIDARG, str.size() != 9 && str.size() != 7 && str.size() != 4);
THROW_HR_IF(E_INVALIDARG, str.at(0) != '#');
THROW_HR_IF(E_INVALIDARG, str.empty() || str.at(0) != '#');
std::string rStr;
std::string gStr;
std::string bStr;
std::string aStr;
wchar_t wch[8];
wch[6] = wch[7] = L'f';
if (str.size() == 4)
switch (str.size())
{
rStr = std::string(2, str.at(1));
gStr = std::string(2, str.at(2));
bStr = std::string(2, str.at(3));
aStr = "ff";
}
else if (str.size() == 7)
{
rStr = std::string(&str.at(1), 2);
gStr = std::string(&str.at(3), 2);
bStr = std::string(&str.at(5), 2);
aStr = "ff";
}
else if (str.size() == 9)
{
// #rrggbbaa
rStr = std::string(&str.at(1), 2);
gStr = std::string(&str.at(3), 2);
bStr = std::string(&str.at(5), 2);
aStr = std::string(&str.at(7), 2);
case 4:
// For RGB we need to widen it to RRGGBB.
wch[0] = wch[1] = str.at(1);
wch[2] = wch[3] = str.at(2);
wch[4] = wch[5] = str.at(3);
break;
case 7:
case 9:
// For RRGGBB or RRGGBBAA we can just copy it directly.
std::copy(str.begin() + 1, str.end(), &wch[0]);
break;
default:
THROW_HR(E_INVALIDARG);
}
const auto r = gsl::narrow_cast<BYTE>(std::stoul(rStr, nullptr, 16));
const auto g = gsl::narrow_cast<BYTE>(std::stoul(gStr, nullptr, 16));
const auto b = gsl::narrow_cast<BYTE>(std::stoul(bStr, nullptr, 16));
const auto a = gsl::narrow_cast<BYTE>(std::stoul(aStr, nullptr, 16));
const auto rgba = til::parse_unsigned<uint32_t>({ &wch[0], 8 }, 16);
THROW_HR_IF(E_INVALIDARG, !rgba);
return til::color{ r, g, b, a };
til::color c;
c.abgr = _byteswap_ulong(*rgba);
return c;
}
// Routine Description:
@ -175,175 +155,103 @@ std::optional<til::color> Utils::ColorFromXTermColor(const std::wstring_view str
// - string - The string containing the color spec string to parse.
// Return Value:
// - An optional color which contains value if a color was successfully parsed
std::optional<til::color> Utils::ColorFromXParseColorSpec(const std::wstring_view string) noexcept
try
std::optional<til::color> Utils::ColorFromXParseColorSpec(std::wstring_view string) noexcept
{
auto foundXParseColorSpec = false;
auto foundValidColorSpec = false;
auto isSharpSignFormat = false;
size_t rgbHexDigitCount = 0;
std::array<unsigned int, 3> colorValues = { 0 };
std::array<unsigned int, 3> parameterValues = { 0 };
const auto stringSize = string.size();
unsigned int parameters[3]{};
// First we look for "rgb:"
// Other colorspaces are theoretically possible, but we don't support them.
auto curr = string.cbegin();
if (stringSize > 4)
if (til::starts_with_insensitive_ascii(string, L"rgb:"))
{
auto prefix = std::wstring(string.substr(0, 4));
// The "rgb:" indicator should be case insensitive. To prevent possible issues under
// different locales, transform only ASCII range latin characters.
std::transform(prefix.begin(), prefix.end(), prefix.begin(), [](const auto x) {
return x >= L'A' && x <= L'Z' ? static_cast<wchar_t>(std::towlower(x)) : x;
});
if (prefix.compare(L"rgb:") == 0)
// If all the components have the same digit count, we can have one of the following formats:
// 9 "rgb:h/h/h"
// 12 "rgb:hh/hh/hh"
// 15 "rgb:hhh/hhh/hhh"
// 18 "rgb:hhhh/hhhh/hhhh"
// Note that the component sizes aren't required to be the same.
// Anything in between is also valid, e.g. "rgb:h/hh/h" and "rgb:h/hh/hhh".
// Any fewer cannot be valid, and any more will be too many. Return early in this case.
if (stringSize < 9 || stringSize > 18)
{
// If all the components have the same digit count, we can have one of the following formats:
// 9 "rgb:h/h/h"
// 12 "rgb:hh/hh/hh"
// 15 "rgb:hhh/hhh/hhh"
// 18 "rgb:hhhh/hhhh/hhhh"
// Note that the component sizes aren't required to be the same.
// Anything in between is also valid, e.g. "rgb:h/hh/h" and "rgb:h/hh/hhh".
// Any fewer cannot be valid, and any more will be too many. Return early in this case.
if (stringSize < 9 || stringSize > 18)
return {};
}
size_t i = 0;
const auto remaining = string.substr(4);
for (auto&& part : til::split_iterator{ remaining, L'/' })
{
if (i >= std::size(parameters) || part.size() < 1 || part.size() > 4)
{
return std::nullopt;
return {};
}
foundXParseColorSpec = true;
const auto val = til::parse_unsigned<unsigned int>(part, 16);
if (!val)
{
return {};
}
std::advance(curr, 4);
auto v = *val;
// Map `v` from its 4/8/12/16-bit range to 8-bit.
const auto bits = static_cast<unsigned int>(part.size() * 4);
// `div` will be 0xf/0xff/0xfff/0xffff respectively.
const auto div = (1ul << bits) - 1;
// Adding `div / 2` will approximately round the value.
v = (v * 255 + div / 2) / div;
parameters[i] = v;
i++;
}
}
// Try the sharp sign format.
if (!foundXParseColorSpec && stringSize > 1)
else if (string.starts_with(L'#'))
{
if (til::at(string, 0) == L'#')
// We can have one of the following formats:
// 4 "#hhh"
// 7 "#hhhhhh"
// 10 "#hhhhhhhhh"
// 13 "#hhhhhhhhhhhh"
// Any other cases will be invalid. Return early in this case.
if (stringSize != 4 && stringSize != 7 && stringSize != 10 && stringSize != 13)
{
// We can have one of the following formats:
// 4 "#hhh"
// 7 "#hhhhhh"
// 10 "#hhhhhhhhh"
// 13 "#hhhhhhhhhhhh"
// Any other cases will be invalid. Return early in this case.
if (!(stringSize == 4 || stringSize == 7 || stringSize == 10 || stringSize == 13))
return {};
}
const auto digits = (stringSize - 1) / 3;
const auto shift = 16 - 4 * digits;
for (size_t i = 0; i < 3; ++i)
{
const auto val = til::parse_unsigned<unsigned int>(string.substr(i * digits + 1, digits), 16);
if (!val)
{
return std::nullopt;
return {};
}
isSharpSignFormat = true;
foundXParseColorSpec = true;
rgbHexDigitCount = (stringSize - 1) / 3;
// > When fewer than 16 bits each are specified, they represent the most significant bits of the value.
// > For example, the string "#3a7" is the same as "#3000a0007000".
// Source: https://www.x.org/releases/current/doc/man/man3/XQueryColor.3.xhtml
// -> Shift the value up.
auto v = *val << shift;
std::advance(curr, 1);
// Now that `v` is 16-bit large we can map it to an 8-bit value.
v = (v * 255 + 0x7fff) / 0xffff;
parameters[i] = v;
}
}
// No valid spec is found. Return early.
if (!foundXParseColorSpec)
else
{
return std::nullopt;
return {};
}
// Try to parse the actual color value of each component.
for (size_t component = 0; component < 3; component++)
{
auto foundColor = false;
auto& parameterValue = til::at(parameterValues, component);
// For "sharp sign" format, the rgbHexDigitCount is known.
// For "rgb:" format, colorspecs are up to hhhh/hhhh/hhhh, for 1-4 h's
const auto iteration = isSharpSignFormat ? rgbHexDigitCount : 4;
for (size_t i = 0; i < iteration && curr < string.cend(); i++)
{
const auto wch = *curr++;
parameterValue *= 16;
unsigned int intVal = 0;
const auto ret = HexToUint(wch, intVal);
if (!ret)
{
// Encountered something weird oh no
return std::nullopt;
}
parameterValue += intVal;
if (isSharpSignFormat)
{
// If we get this far, any number can be seen as a valid part
// of this component.
foundColor = true;
if (i >= rgbHexDigitCount)
{
// Successfully parsed this component. Start the next one.
break;
}
}
else
{
// Record the hex digit count of the current component.
rgbHexDigitCount = i + 1;
// If this is the first 2 component...
if (component < 2 && curr < string.cend() && *curr == L'/')
{
// ...and we have successfully parsed this component, we need
// to skip the delimiter before starting the next one.
curr++;
foundColor = true;
break;
}
// Or we have reached the end of the string...
else if (curr >= string.cend())
{
// ...meaning that this is the last component. We're not going to
// see any delimiter. We can just break out.
foundColor = true;
break;
}
}
}
if (!foundColor)
{
// Indicates there was some error parsing color.
return std::nullopt;
}
// Calculate the actual color value based on the hex digit count.
auto& colorValue = til::at(colorValues, component);
const auto scaleMultiplier = isSharpSignFormat ? 0x10 : 0x11;
const auto scaleDivisor = scaleMultiplier << 8 >> 4 * (4 - rgbHexDigitCount);
colorValue = parameterValue * scaleMultiplier / scaleDivisor;
}
if (curr >= string.cend())
{
// We're at the end of the string and we have successfully parsed the color.
foundValidColorSpec = true;
}
// Only if we find a valid colorspec can we pass it out successfully.
if (foundValidColorSpec)
{
return til::color(LOBYTE(til::at(colorValues, 0)),
LOBYTE(til::at(colorValues, 1)),
LOBYTE(til::at(colorValues, 2)));
}
return std::nullopt;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return std::nullopt;
return til::color(LOBYTE(til::at(parameters, 0)),
LOBYTE(til::at(parameters, 1)),
LOBYTE(til::at(parameters, 2)));
}
// Routine Description:
@ -506,74 +414,6 @@ std::tuple<int, int, int> Utils::ColorToHLS(const til::color color) noexcept
return { h, l, s };
}
// Routine Description:
// - Converts a hex character to its equivalent integer value.
// Arguments:
// - wch - Character to convert.
// - value - receives the int value of the char
// Return Value:
// - true iff the character is a hex character.
bool Utils::HexToUint(const wchar_t wch,
unsigned int& value) noexcept
{
value = 0;
auto success = false;
if (wch >= L'0' && wch <= L'9')
{
value = wch - L'0';
success = true;
}
else if (wch >= L'A' && wch <= L'F')
{
value = (wch - L'A') + 10;
success = true;
}
else if (wch >= L'a' && wch <= L'f')
{
value = (wch - L'a') + 10;
success = true;
}
return success;
}
// Routine Description:
// - Converts a number string to its equivalent unsigned integer value.
// Arguments:
// - wstr - String to convert.
// - value - receives the int value of the string
// Return Value:
// - true iff the string is a unsigned integer string.
bool Utils::StringToUint(const std::wstring_view wstr,
unsigned int& value)
{
if (wstr.size() < 1)
{
return false;
}
unsigned int result = 0;
size_t current = 0;
while (current < wstr.size())
{
const auto wch = wstr.at(current);
if (_isNumber(wch))
{
result *= 10;
result += wch - L'0';
++current;
}
else
{
return false;
}
}
value = result;
return true;
}
// Routine Description:
// - Split a string into different parts using the delimiter provided.
// Arguments:
@ -581,34 +421,14 @@ bool Utils::StringToUint(const std::wstring_view wstr,
// - delimiter - delimiter to use.
// Return Value:
// - a vector containing the result string parts.
std::vector<std::wstring_view> Utils::SplitString(const std::wstring_view wstr,
const wchar_t delimiter) noexcept
til::small_vector<std::wstring_view, 4> Utils::SplitString(std::wstring_view wstr, const wchar_t delimiter) noexcept
try
{
std::vector<std::wstring_view> result;
size_t current = 0;
while (current < wstr.size())
til::small_vector<std::wstring_view, 4> result;
for (auto&& part : til::split_iterator{ wstr, delimiter })
{
const auto nextDelimiter = wstr.find(delimiter, current);
if (nextDelimiter == std::wstring::npos)
{
result.push_back(wstr.substr(current));
break;
}
else
{
const auto length = nextDelimiter - current;
result.push_back(wstr.substr(current, length));
// Skip this part and the delimiter. Start the next one
current += length + 1;
// The next index is larger than string size, which means the string
// is in the format of "part1;part2;" (assuming use ';' as delimiter).
// Add the last part which is an empty string.
if (current >= wstr.size())
{
result.push_back(L"");
}
}
result.push_back(part);
}
return result;
@ -925,6 +745,13 @@ HRESULT Utils::GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD
// - a new stable v5 UUID
GUID Utils::CreateV5Uuid(const GUID& namespaceGuid, const std::span<const std::byte> name)
{
static constexpr auto EndianSwap = [](GUID value) {
value.Data1 = _byteswap_ulong(value.Data1);
value.Data2 = _byteswap_ushort(value.Data2);
value.Data3 = _byteswap_ushort(value.Data3);
return value;
};
// v5 uuid generation happens over values in network byte order, so let's enforce that
auto correctEndianNamespaceGuid{ EndianSwap(namespaceGuid) };