mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-06 14:19:45 +00:00
Compare commits
2 Commits
dev/duhowe
...
dev/lhecke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e4f03c35a | ||
|
|
1daa180b22 |
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,10 +162,9 @@ winrt::com_ptr<IShellLinkW> Jumplist::_createShellLink(const std::wstring_view n
|
||||
const std::wstring iconPath{ path.substr(0, commaPosition) };
|
||||
|
||||
// We dont want the comma included so add 1 to its position
|
||||
int iconIndex = til::to_int(path.substr(commaPosition + 1));
|
||||
if (iconIndex != til::to_int_error)
|
||||
if (const auto iconIndex = til::parse_signed<int>(path.substr(commaPosition + 1)))
|
||||
{
|
||||
THROW_IF_FAILED(sh->SetIconLocation(iconPath.data(), iconIndex));
|
||||
THROW_IF_FAILED(sh->SetIconLocation(iconPath.data(), *iconIndex));
|
||||
}
|
||||
}
|
||||
else if (til::ends_with(path, L"exe") || til::ends_with(path, L"dll"))
|
||||
|
||||
@@ -101,10 +101,10 @@ static int32_t parseNumericCode(const std::wstring_view& str, const std::wstring
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto value = til::to_ulong({ str.data() + prefix.size(), str.size() - prefix.size() - suffix.size() });
|
||||
if (value > 0 && value < 256)
|
||||
const auto value = til::parse_unsigned<uint8_t>({ str.data() + prefix.size(), str.size() - prefix.size() - suffix.size() });
|
||||
if (value)
|
||||
{
|
||||
return gsl::narrow_cast<int32_t>(value);
|
||||
return gsl::narrow_cast<int32_t>(*value);
|
||||
}
|
||||
|
||||
throw winrt::hresult_invalid_argument(L"Invalid numeric argument to vk() or sc()");
|
||||
@@ -151,10 +151,8 @@ static KeyChord _fromString(std::wstring_view wstr)
|
||||
auto vkey = 0;
|
||||
auto scanCode = 0;
|
||||
|
||||
while (!wstr.empty())
|
||||
for (auto&& part : til::split_iterator{ wstr, L'+' })
|
||||
{
|
||||
const auto part = til::prefix_split(wstr, L'+');
|
||||
|
||||
if (til::equals_insensitive_ascii(part, CTRL_KEY))
|
||||
{
|
||||
modifiers |= VirtualKeyModifiers::Control;
|
||||
|
||||
@@ -719,8 +719,8 @@ struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<::winr
|
||||
if (isIndexed16)
|
||||
{
|
||||
const auto indexStr = string.substr(1);
|
||||
const auto idx = til::to_ulong(indexStr, 16);
|
||||
color.r = gsl::narrow_cast<uint8_t>(std::min(idx, 15ul));
|
||||
const auto idx = til::parse_unsigned<uint8_t>(indexStr, 16);
|
||||
color.r = std::min<uint8_t>(idx.value_or(255), 15);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -73,35 +73,33 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
|
||||
double Converters::MaxValueFromPaddingString(const winrt::hstring& paddingString)
|
||||
{
|
||||
std::wstring_view remaining{ paddingString };
|
||||
std::wstring_view input{ paddingString };
|
||||
std::wstring buffer;
|
||||
double maxVal = 0;
|
||||
|
||||
auto& errnoRef = errno; // Nonzero cost, pay it once
|
||||
|
||||
// Get padding values till we run out of delimiter separated values in the stream
|
||||
// Non-numeral values detected will default to 0
|
||||
// std::stod will throw invalid_argument exception if the input is an invalid double value
|
||||
// std::stod will throw out_of_range exception if the input value is more than DBL_MAX
|
||||
try
|
||||
for (auto&& part : til::split_iterator{ input, L',' })
|
||||
{
|
||||
while (!remaining.empty())
|
||||
buffer.assign(part);
|
||||
|
||||
// wcstod handles whitespace prefix (which is ignored) & stops the
|
||||
// scan when first char outside the range of radix is encountered.
|
||||
// We'll be permissive till the extent that stod function allows us to be by default
|
||||
// Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail
|
||||
errnoRef = 0;
|
||||
wchar_t* end;
|
||||
const double val = wcstod(buffer.c_str(), &end);
|
||||
|
||||
if (end != buffer.c_str() && errnoRef != ERANGE)
|
||||
{
|
||||
const std::wstring token{ til::prefix_split(remaining, L',') };
|
||||
// std::stod internally calls wcstod which handles whitespace prefix (which is ignored)
|
||||
// & stops the scan when first char outside the range of radix is encountered
|
||||
// We'll be permissive till the extent that stod function allows us to be by default
|
||||
// Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail
|
||||
const auto curVal = std::stod(token);
|
||||
if (curVal > maxVal)
|
||||
{
|
||||
maxVal = curVal;
|
||||
}
|
||||
maxVal = std::max(maxVal, val);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If something goes wrong, even if due to a single bad padding value, we'll return default 0 padding
|
||||
maxVal = 0;
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
}
|
||||
|
||||
return maxVal;
|
||||
}
|
||||
|
||||
@@ -271,12 +271,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
if (commaIndex != std::wstring::npos)
|
||||
{
|
||||
// Convert the string iconIndex to a signed int to support negative numbers which represent an Icon's ID.
|
||||
const auto index{ til::to_int(pathView.substr(commaIndex + 1)) };
|
||||
if (index == til::to_int_error)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<int>(index);
|
||||
return til::parse_signed<int>(pathView.substr(commaIndex + 1));
|
||||
}
|
||||
|
||||
// We had a binary path, but no index. Default to 0.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
127
src/inc/til/point_span.h
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -133,113 +133,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return ends_with<>(str, prefix);
|
||||
}
|
||||
|
||||
inline constexpr unsigned long to_ulong_error = ULONG_MAX;
|
||||
inline constexpr int to_int_error = INT_MAX;
|
||||
|
||||
// Just like std::wcstoul, but without annoying locales and null-terminating strings.
|
||||
// It has been fuzz-tested against clang's strtoul implementation.
|
||||
template<typename T, typename Traits>
|
||||
_TIL_INLINEPREFIX constexpr unsigned long to_ulong(const std::basic_string_view<T, Traits>& str, unsigned long base = 0) noexcept
|
||||
{
|
||||
static constexpr unsigned long maximumValue = ULONG_MAX / 16;
|
||||
|
||||
// We don't have to test ptr for nullability, as we only access it under either condition:
|
||||
// * str.length() > 0, for determining the base
|
||||
// * ptr != end, when parsing the characters; if ptr is null, length will be 0 and thus end == ptr
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26429) // Symbol 'ptr' is never tested for nullness, it can be marked as not_null
|
||||
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead
|
||||
auto ptr = str.data();
|
||||
const auto end = ptr + str.length();
|
||||
unsigned long accumulator = 0;
|
||||
unsigned long value = ULONG_MAX;
|
||||
|
||||
if (!base)
|
||||
{
|
||||
base = 10;
|
||||
|
||||
if (str.length() > 1 && *ptr == '0')
|
||||
{
|
||||
base = 8;
|
||||
++ptr;
|
||||
|
||||
if (str.length() > 2 && (*ptr == 'x' || *ptr == 'X'))
|
||||
{
|
||||
base = 16;
|
||||
++ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == end)
|
||||
{
|
||||
return to_ulong_error;
|
||||
}
|
||||
|
||||
for (;; accumulator *= base)
|
||||
{
|
||||
value = ULONG_MAX;
|
||||
if (*ptr >= '0' && *ptr <= '9')
|
||||
{
|
||||
value = *ptr - '0';
|
||||
}
|
||||
else if (*ptr >= 'A' && *ptr <= 'F')
|
||||
{
|
||||
value = *ptr - 'A' + 10;
|
||||
}
|
||||
else if (*ptr >= 'a' && *ptr <= 'f')
|
||||
{
|
||||
value = *ptr - 'a' + 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
return to_ulong_error;
|
||||
}
|
||||
|
||||
accumulator += value;
|
||||
if (accumulator >= maximumValue)
|
||||
{
|
||||
return to_ulong_error;
|
||||
}
|
||||
|
||||
if (++ptr == end)
|
||||
{
|
||||
return accumulator;
|
||||
}
|
||||
}
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
constexpr unsigned long to_ulong(const std::string_view& str, unsigned long base = 0) noexcept
|
||||
{
|
||||
return to_ulong<>(str, base);
|
||||
}
|
||||
|
||||
constexpr unsigned long to_ulong(const std::wstring_view& str, unsigned long base = 0) noexcept
|
||||
{
|
||||
return to_ulong<>(str, base);
|
||||
}
|
||||
|
||||
// Implement to_int in terms of to_ulong by negating its result. to_ulong does not expect
|
||||
// to be passed signed numbers and will return an error accordingly. That error when
|
||||
// compared against -1 evaluates to true. We account for that by returning to_int_error if to_ulong
|
||||
// returns an error.
|
||||
constexpr int to_int(const std::wstring_view& str, unsigned long base = 0) noexcept
|
||||
{
|
||||
auto result = to_ulong_error;
|
||||
const auto signPosition = str.find(L"-");
|
||||
const bool hasSign = signPosition != std::wstring_view::npos;
|
||||
result = hasSign ? to_ulong(str.substr(signPosition + 1), base) : to_ulong(str, base);
|
||||
|
||||
// Check that result is valid and will fit in an int.
|
||||
if (result == to_ulong_error || (result > INT_MAX))
|
||||
{
|
||||
return to_int_error;
|
||||
}
|
||||
|
||||
return hasSign ? result * -1 : result;
|
||||
}
|
||||
|
||||
// Just like std::tolower, but without annoying locales.
|
||||
template<typename T>
|
||||
constexpr T tolower_ascii(T c)
|
||||
@@ -348,55 +241,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return ends_with<>(str, prefix);
|
||||
}
|
||||
|
||||
// Give the arguments ("foo bar baz", " "), this method will
|
||||
// * modify the first argument to "bar baz"
|
||||
// * return "foo"
|
||||
// If the needle cannot be found the "str" argument is returned as is.
|
||||
template<typename T, typename Traits>
|
||||
constexpr std::basic_string_view<T, Traits> prefix_split(std::basic_string_view<T, Traits>& str, const std::basic_string_view<T, Traits>& needle) noexcept
|
||||
{
|
||||
using view_type = std::basic_string_view<T, Traits>;
|
||||
|
||||
const auto needleLen = needle.size();
|
||||
const auto idx = needleLen == 0 ? str.size() : str.find(needle);
|
||||
const auto prefixIdx = std::min(str.size(), idx);
|
||||
const auto suffixIdx = std::min(str.size(), prefixIdx + needle.size());
|
||||
|
||||
const view_type result{ str.data(), prefixIdx };
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead
|
||||
str = { str.data() + suffixIdx, str.size() - suffixIdx };
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr std::string_view prefix_split(std::string_view& str, const std::string_view& needle) noexcept
|
||||
{
|
||||
return prefix_split<>(str, needle);
|
||||
}
|
||||
|
||||
constexpr std::wstring_view prefix_split(std::wstring_view& str, const std::wstring_view& needle) noexcept
|
||||
{
|
||||
return prefix_split<>(str, needle);
|
||||
}
|
||||
|
||||
// Give the arguments ("foo bar baz", " "), this method will
|
||||
// * modify the first argument to "bar baz"
|
||||
// * return "foo"
|
||||
// If the needle cannot be found the "str" argument is returned as is.
|
||||
template<typename T, typename Traits>
|
||||
constexpr std::basic_string_view<T, Traits> prefix_split(std::basic_string_view<T, Traits>& str, T ch) noexcept
|
||||
{
|
||||
using view_type = std::basic_string_view<T, Traits>;
|
||||
|
||||
const auto idx = str.find(ch);
|
||||
const auto prefixIdx = std::min(str.size(), idx);
|
||||
const auto suffixIdx = std::min(str.size(), prefixIdx + 1);
|
||||
|
||||
const view_type result{ str.data(), prefixIdx };
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead
|
||||
str = { str.data() + suffixIdx, str.size() - suffixIdx };
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T, typename Traits>
|
||||
constexpr std::basic_string_view<T, Traits> trim(const std::basic_string_view<T, Traits>& str, const T ch) noexcept
|
||||
{
|
||||
@@ -414,6 +258,228 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return { beg, end };
|
||||
}
|
||||
|
||||
template<typename T, typename Traits>
|
||||
struct split_iterator
|
||||
{
|
||||
struct sentinel
|
||||
{
|
||||
};
|
||||
|
||||
struct iterator
|
||||
{
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = std::basic_string_view<T, Traits>;
|
||||
using reference = value_type&;
|
||||
using pointer = value_type*;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
explicit constexpr iterator(split_iterator& p) noexcept :
|
||||
_iter{ p }
|
||||
{
|
||||
}
|
||||
|
||||
value_type operator*() const noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
// Don't call operator*() twice per iteration.
|
||||
// Since C++ has inflexible iterators we can't implement a split iterator efficiently:
|
||||
// We essentially need a do-while loop but iterators only offer for() loop semantics.
|
||||
// This forces us to either seek to the first token in the constructor and then again on
|
||||
// every operator++(), or we do this trick here where we seek on every operator*() call.
|
||||
// This only works well of course, if you don't call operator*() multiple times.
|
||||
assert(_iter._advanced);
|
||||
_iter._advanced = false;
|
||||
#endif
|
||||
_iter._tok = std::find(_iter._it, _iter._end, _iter._needle);
|
||||
return { _iter._it, _iter._tok };
|
||||
}
|
||||
|
||||
iterator& operator++() noexcept
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
_iter._advanced = true;
|
||||
#endif
|
||||
_iter._it = std::min(_iter._end - 1, _iter._tok) + 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!=(const sentinel&) const noexcept
|
||||
{
|
||||
return _iter._tok != _iter._end;
|
||||
}
|
||||
|
||||
private:
|
||||
split_iterator& _iter;
|
||||
};
|
||||
|
||||
explicit constexpr split_iterator(const std::basic_string_view<T, Traits>& str, T needle) noexcept :
|
||||
_it{ str.begin() },
|
||||
_tok{ str.begin() },
|
||||
_end{ str.end() },
|
||||
_needle{ needle }
|
||||
{
|
||||
}
|
||||
|
||||
iterator begin() noexcept
|
||||
{
|
||||
return iterator{ *this };
|
||||
}
|
||||
|
||||
sentinel end() noexcept
|
||||
{
|
||||
return sentinel{};
|
||||
}
|
||||
|
||||
private:
|
||||
typename iterator::value_type::iterator _it;
|
||||
typename iterator::value_type::iterator _tok;
|
||||
typename iterator::value_type::iterator _end;
|
||||
typename iterator::value_type _value;
|
||||
T _needle;
|
||||
#ifndef NDEBUG
|
||||
bool _advanced = true;
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace details
|
||||
{
|
||||
// Just like std::wcstoul, but without annoying locales and null-terminating strings.
|
||||
template<typename T, typename Traits>
|
||||
_TIL_INLINEPREFIX constexpr std::optional<uint64_t> parse_u64(const std::basic_string_view<T, Traits>& str, int base = 0) noexcept
|
||||
{
|
||||
// We don't have to test ptr for nullability, as we only access it under either condition:
|
||||
// * str.length() > 0, for determining the base
|
||||
// * ptr != end, when parsing the characters; if ptr is null, length will be 0 and thus end == ptr
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26429) // Symbol 'ptr' is never tested for nullness, it can be marked as not_null
|
||||
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead
|
||||
auto ptr = str.data();
|
||||
const auto end = ptr + str.length();
|
||||
uint64_t accumulator = 0;
|
||||
uint64_t base64 = base;
|
||||
|
||||
if (base <= 0)
|
||||
{
|
||||
base64 = 10;
|
||||
|
||||
if (ptr != end && *ptr == '0')
|
||||
{
|
||||
base64 = 8;
|
||||
ptr += 1;
|
||||
|
||||
if (ptr != end && (*ptr == 'x' || *ptr == 'X'))
|
||||
{
|
||||
base64 = 16;
|
||||
ptr += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == end)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
uint64_t value = 0;
|
||||
if (*ptr >= '0' && *ptr <= '9')
|
||||
{
|
||||
value = *ptr - '0';
|
||||
}
|
||||
else if (*ptr >= 'A' && *ptr <= 'F')
|
||||
{
|
||||
value = *ptr - 'A' + 10;
|
||||
}
|
||||
else if (*ptr >= 'a' && *ptr <= 'f')
|
||||
{
|
||||
value = *ptr - 'a' + 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto acc = accumulator * base64 + value;
|
||||
if (acc < accumulator)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
accumulator = acc;
|
||||
ptr += 1;
|
||||
|
||||
if (ptr == end)
|
||||
{
|
||||
return accumulator;
|
||||
}
|
||||
}
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
template<std::unsigned_integral R, typename T, typename Traits>
|
||||
constexpr std::optional<R> parse_unsigned(const std::basic_string_view<T, Traits>& str, int base = 0) noexcept
|
||||
{
|
||||
if constexpr (std::is_same_v<R, uint64_t>)
|
||||
{
|
||||
return details::parse_u64<>(str, base);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto opt = details::parse_u64<>(str, base);
|
||||
if (!opt || *opt > uint64_t{ std::numeric_limits<R>::max() })
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return gsl::narrow_cast<R>(*opt);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::signed_integral R, typename T, typename Traits>
|
||||
constexpr std::optional<R> parse_signed(std::basic_string_view<T, Traits> str, int base = 0) noexcept
|
||||
{
|
||||
const bool hasSign = str.starts_with(L'-');
|
||||
if (hasSign)
|
||||
{
|
||||
str = str.substr(1);
|
||||
}
|
||||
|
||||
const auto opt = details::parse_u64<>(str, base);
|
||||
const auto max = uint64_t{ std::numeric_limits<R>::max() } + hasSign;
|
||||
if (!opt || *opt > max)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto r = gsl::narrow_cast<R>(*opt);
|
||||
return hasSign ? -r : r;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
constexpr std::optional<R> parse_unsigned(const std::string_view& str, int base = 0) noexcept
|
||||
{
|
||||
return details::parse_unsigned<R>(str, base);
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
constexpr std::optional<R> parse_unsigned(const std::wstring_view& str, int base = 0) noexcept
|
||||
{
|
||||
return details::parse_unsigned<R>(str, base);
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
constexpr std::optional<R> parse_signed(const std::string_view& str, int base = 0) noexcept
|
||||
{
|
||||
return details::parse_signed<R>(str, base);
|
||||
}
|
||||
|
||||
template<typename R>
|
||||
constexpr std::optional<R> parse_signed(const std::wstring_view& str, int base = 0) noexcept
|
||||
{
|
||||
return details::parse_signed<R>(str, base);
|
||||
}
|
||||
|
||||
// Splits a font-family list into individual font-families. It loosely follows the CSS spec for font-family.
|
||||
// It splits by comma, handles quotes and simple escape characters, and it cleans whitespace.
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ class StringTests
|
||||
VERIFY_IS_TRUE(til::ends_with("0abc", "abc"));
|
||||
}
|
||||
|
||||
// Normally this would be the spot where you'd find a TEST_METHOD(to_ulong).
|
||||
// Normally this would be the spot where you'd find a TEST_METHOD(parse_u64).
|
||||
// I didn't quite trust my coding skills and thus opted to use fuzz-testing.
|
||||
// The below function was used to test to_ulong for unsafety and conformance with clang's strtoul.
|
||||
// The below function was used to test parse_u64 for unsafety and conformance with clang's strtoul.
|
||||
// The test was run as:
|
||||
// clang++ -fsanitize=address,undefined,fuzzer -std=c++17 file.cpp
|
||||
// and was run for 20min across 16 jobs in parallel.
|
||||
@@ -112,7 +112,7 @@ class StringTests
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto actual = to_ulong({ wide_buffer, size });
|
||||
const auto actual = parse_u64({ wide_buffer, size });
|
||||
if (expected != actual)
|
||||
{
|
||||
__builtin_trap();
|
||||
@@ -147,64 +147,39 @@ class StringTests
|
||||
VERIFY_IS_TRUE(til::equals_insensitive_ascii("cOUnterStriKE", "COuntERStRike"));
|
||||
}
|
||||
|
||||
TEST_METHOD(prefix_split)
|
||||
TEST_METHOD(split_iterator)
|
||||
{
|
||||
{
|
||||
std::string_view s{ "" };
|
||||
VERIFY_ARE_EQUAL("", til::prefix_split(s, ""));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
}
|
||||
{
|
||||
std::string_view s{ "" };
|
||||
VERIFY_ARE_EQUAL("", til::prefix_split(s, " "));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
}
|
||||
{
|
||||
std::string_view s{ " " };
|
||||
VERIFY_ARE_EQUAL(" ", til::prefix_split(s, ""));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
}
|
||||
{
|
||||
std::string_view s{ "foo" };
|
||||
VERIFY_ARE_EQUAL("foo", til::prefix_split(s, ""));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
}
|
||||
{
|
||||
std::string_view s{ "foo bar baz" };
|
||||
VERIFY_ARE_EQUAL("foo", til::prefix_split(s, " "));
|
||||
VERIFY_ARE_EQUAL("bar baz", s);
|
||||
VERIFY_ARE_EQUAL("bar", til::prefix_split(s, " "));
|
||||
VERIFY_ARE_EQUAL("baz", s);
|
||||
VERIFY_ARE_EQUAL("baz", til::prefix_split(s, " "));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
}
|
||||
{
|
||||
std::string_view s{ "foo123barbaz123" };
|
||||
VERIFY_ARE_EQUAL("foo", til::prefix_split(s, "123"));
|
||||
VERIFY_ARE_EQUAL("barbaz123", s);
|
||||
VERIFY_ARE_EQUAL("barbaz", til::prefix_split(s, "123"));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
VERIFY_ARE_EQUAL("", til::prefix_split(s, ""));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
}
|
||||
}
|
||||
static constexpr auto split = [](const std::string_view& str, const char needle) {
|
||||
std::vector<std::string_view> substrings;
|
||||
for (auto&& s : til::split_iterator{ str, needle })
|
||||
{
|
||||
substrings.emplace_back(s);
|
||||
}
|
||||
return substrings;
|
||||
};
|
||||
|
||||
TEST_METHOD(prefix_split_char)
|
||||
{
|
||||
{
|
||||
std::string_view s{ "" };
|
||||
VERIFY_ARE_EQUAL("", til::prefix_split(s, ' '));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
}
|
||||
{
|
||||
std::string_view s{ "foo bar baz" };
|
||||
VERIFY_ARE_EQUAL("foo", til::prefix_split(s, ' '));
|
||||
VERIFY_ARE_EQUAL("bar baz", s);
|
||||
VERIFY_ARE_EQUAL("bar", til::prefix_split(s, ' '));
|
||||
VERIFY_ARE_EQUAL("baz", s);
|
||||
VERIFY_ARE_EQUAL("baz", til::prefix_split(s, ' '));
|
||||
VERIFY_ARE_EQUAL("", s);
|
||||
}
|
||||
std::vector<std::string_view> expected;
|
||||
std::vector<std::string_view> actual;
|
||||
|
||||
expected = { "foo" };
|
||||
actual = split("foo", ' ');
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
expected = { "", "foo" };
|
||||
actual = split(" foo", ' ');
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
expected = { "foo", "" };
|
||||
actual = split("foo ", ' ');
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
expected = { "foo", "bar", "baz" };
|
||||
actual = split("foo bar baz", ' ');
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
|
||||
expected = { "", "", "foo", "", "bar", "", "" };
|
||||
actual = split(";;foo;;bar;;", ';');
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
TEST_METHOD(CleanPathAndFilename)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user