From 50508964b67a63507e19e919a3ee487a6e436cbb Mon Sep 17 00:00:00 2001 From: Anthony Leedom Date: Sat, 3 Feb 2024 12:53:08 -0800 Subject: [PATCH] Complete Glyph free function ops implementations --- include/esc/glyph.hpp | 250 +++++++++++++++++++++++--- tests/glyph.test.cpp | 400 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 624 insertions(+), 26 deletions(-) diff --git a/include/esc/glyph.hpp b/include/esc/glyph.hpp index a1cafd6..ffea1b3 100644 --- a/include/esc/glyph.hpp +++ b/include/esc/glyph.hpp @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include #include #include #include @@ -44,9 +46,8 @@ concept Character = * Defines the requirements for a type to be considered an Attribute. */ template -concept Attribute = - std::same_as || std::same_as || - std::same_as || std::same_as; +concept Attribute = std::same_as || std::same_as || + std::same_as || std::same_as; /** * Defines the requirements for a type to be considered a GlyphString. @@ -55,6 +56,65 @@ template concept GlyphString = std::ranges::forward_range && std::same_as, Glyph>; +/** + * Defines the requirements for a type that is a GlyphString and can have Glyphs + * inserted into it. + */ +template +concept InsertableGlyphString = GlyphString && requires(T& t, Glyph g) { + { + *std::inserter(t, std::begin(t)) = g + } -> std::same_as&>; +}; + +// ------------------------------ HELPERS -------------------------------------- + +namespace detail { + +/** + * Creates a std::vector from the given UTF8 string_view. + * + * @param sv The string_view to create Glyphs from. + * @return A std::vector with each code point as a Glyph. + * @throws std::runtime_error If the input string is not valid UTF-8. + */ +[[nodiscard]] inline auto utf8_to_glyphs(std::string_view sv) + -> std::vector +{ + auto glyphs = std::vector{}; + auto i = std::int32_t{0}; // Index in the UTF-8 string + auto const length = static_cast(sv.length()); + + while (i < length) { + UChar32 ch; + U8_NEXT(sv.data(), i, length, ch); + if (ch < 0) { + throw std::runtime_error{"Invalid UTF-8 sequence"}; + } + glyphs.push_back({static_cast(ch)}); + } + + return glyphs; +} + +/** + * Creates a std::vector from the given UTF32 string_view. + * + * @param sv The string_view to create Glyphs from. + * @return A std::vector with each code point as a Glyph. + */ +[[nodiscard]] inline auto utf32_to_glyphs(std::u32string_view sv) + -> std::vector +{ + auto glyphs = std::vector{}; + glyphs.reserve(sv.length()); + std::ranges::transform(sv, std::back_inserter(glyphs), + [](auto ch) { return Glyph{.symbol = ch}; }); + return glyphs; +} + +} // namespace detail + // ------------------------------ PIPE OPS ------------------------------------- // ------------------------------ COLORBG -------------------------------------- @@ -65,7 +125,7 @@ concept GlyphString = std::ranges::forward_range && * @param c The ColorBG to apply to the return Glyph's brush. * @return A Glyph with the given symbol and Color background. */ -[[nodiscard]] constexpr auto operator|(Character auto symbol, esc::ColorBG c) +[[nodiscard]] constexpr auto operator|(Character auto symbol, ColorBG c) -> Glyph { return {static_cast(symbol), {.background = c.value}}; @@ -78,7 +138,7 @@ concept GlyphString = std::ranges::forward_range && * @param c The ColorBG to apply to the return Glyph's brush. * @return The passed in Glyph with the given Color background. */ -[[nodiscard]] constexpr auto operator|(Glyph g, esc::ColorBG c) -> Glyph +[[nodiscard]] constexpr auto operator|(Glyph g, ColorBG c) -> Glyph { g.brush.background = c.value; return g; @@ -92,7 +152,7 @@ concept GlyphString = std::ranges::forward_range && * @return The passed in GlyphString with the given Color background. */ template -auto operator|(T&& gs, esc::ColorBG c) -> decltype(auto) +auto operator|(T&& gs, ColorBG c) -> decltype(auto) { for (auto& glyph : gs) { glyph.brush.background = c.value; @@ -109,8 +169,8 @@ auto operator|(T&& gs, esc::ColorBG c) -> decltype(auto) * @param c The ColorFG to apply to the return Glyph's brush. * @return A Glyph with the given symbol and Color foreground. */ -[[nodiscard]] constexpr auto operator|(Character auto symbol, esc::ColorFG c) - -> Glyph +template +[[nodiscard]] constexpr auto operator|(T symbol, ColorFG c) -> Glyph { return {static_cast(symbol), {.foreground = c.value}}; } @@ -122,7 +182,7 @@ auto operator|(T&& gs, esc::ColorBG c) -> decltype(auto) * @param c The ColorFG to apply to the return Glyph's brush. * @return The passed in Glyph with the given Color foreground. */ -[[nodiscard]] constexpr auto operator|(Glyph g, esc::ColorFG c) -> Glyph +[[nodiscard]] constexpr auto operator|(Glyph g, ColorFG c) -> Glyph { g.brush.foreground = c.value; return g; @@ -136,7 +196,7 @@ auto operator|(T&& gs, esc::ColorBG c) -> decltype(auto) * @return The passed in GlyphString with the given Color foreground. */ template -auto operator|(T&& gs, esc::ColorFG c) -> decltype(auto) +auto operator|(T&& gs, ColorFG c) -> decltype(auto) { for (auto& glyph : gs) { glyph.brush.foreground = c.value; @@ -192,14 +252,14 @@ auto operator|(T&& gs, Trait t) -> decltype(auto) /** * Removes a Trait from a Glyph's brush. * - * @details Use the esc::remove_trait(Trait) function to create the RemoveTrait + * @details Use the remove_trait(Trait) function to create the RemoveTrait * object. * * @param g The Glyph to remove the Trait from. * @param t The Trait to remove from the Glyph's brush. * @return The Glyph with the Trait removed from its brush. */ -[[nodiscard]] constexpr auto operator|(Glyph g, esc::RemoveTrait t) -> Glyph +[[nodiscard]] constexpr auto operator|(Glyph g, RemoveTrait t) -> Glyph { g.brush.traits = g.brush.traits | t; return g; @@ -213,7 +273,7 @@ auto operator|(T&& gs, Trait t) -> decltype(auto) * @return The passed in GlyphString with the given Trait removed. */ template -auto operator|(T&& gs, esc::RemoveTrait t) -> decltype(auto) +auto operator|(T&& gs, RemoveTrait t) -> decltype(auto) { for (auto& glyph : gs) { glyph.brush.traits = glyph.brush.traits | t; @@ -234,12 +294,7 @@ template [[nodiscard]] auto operator|(std::u32string_view sv, T attr) -> std::vector { - auto glyphs = std::vector{}; - glyphs.reserve(sv.length()); - for (auto const symbol : sv) { - glyphs.push_back({symbol}); - } - return glyphs | attr; + return detail::utf32_to_glyphs(sv) | attr; } /** @@ -249,28 +304,171 @@ template * * @param sv The string_view to create Glyphs from. * @param attr The Attribute to apply to each Glyph's brush. - * @return A std::vector with the given Attribute applied to each - * Glyph. + * @return A std::vector with the given Attribute applied to each Glyph. * @throws std::runtime_error If the input string is not valid UTF-8. */ template [[nodiscard]] auto operator|(std::string_view sv, T attr) -> std::vector { - auto glyphs = std::vector{}; + return detail::utf8_to_glyphs(sv) | attr; +} + +// ----------------------------- OPERATOR+= ------------------------------------ + +template +auto operator+=(T& lhs, U const& rhs) -> T& +{ + lhs.reserve(lhs.size() + rhs.size()); + std::ranges::copy(rhs, std::inserter(lhs, std::end(lhs))); + return lhs; +} + +template +auto operator+=(T& lhs, std::u32string_view rhs) -> T& +{ + lhs.reserve(lhs.size() + rhs.size()); + std::ranges::transform(rhs, std::inserter(lhs, std::end(lhs)), + [](auto ch) { return Glyph{.symbol = ch}; }); + return lhs; +} +template +auto operator+=(T& lhs, std::string_view rhs) -> T& +{ auto i = std::int32_t{0}; // Index in the UTF-8 string - auto const length = static_cast(sv.length()); + auto const length = static_cast(rhs.length()); + auto inserter = std::inserter(lhs, std::end(lhs)); while (i < length) { UChar32 ch; - U8_NEXT(sv.data(), i, length, ch); + U8_NEXT(rhs.data(), i, length, ch); if (ch < 0) { throw std::runtime_error{"Invalid UTF-8 sequence"}; } - glyphs.push_back({static_cast(ch)}); + *inserter = Glyph{.symbol = static_cast(ch)}; + } + + return lhs; +} + +template +auto operator+=(T& lhs, Glyph const& rhs) -> T& +{ + lhs.insert(std::end(lhs), rhs); + return lhs; +} + +template +auto operator+=(T& lhs, U rhs) -> T& +{ + lhs.insert(std::end(lhs), {.symbol = static_cast(rhs)}); + return lhs; +} + +// ----------------------------- OPERATOR+ ------------------------------------- + +[[nodiscard]] inline auto operator+(Glyph const& lhs, Glyph const& rhs) + -> std::vector +{ + return {lhs, rhs}; +} + +template +[[nodiscard]] auto operator+(Glyph const& lhs, T rhs) -> std::vector +{ + return {lhs, {.symbol = static_cast(rhs)}}; +} + +template +[[nodiscard]] auto operator+(T lhs, Glyph const& rhs) -> std::vector +{ + return {{.symbol = static_cast(lhs)}, rhs}; +} + +template +[[nodiscard]] auto operator+(T lhs, std::string_view rhs) -> T +{ + return lhs += rhs; +} + +template +[[nodiscard]] auto operator+(std::string_view lhs, T rhs) -> T +{ + auto i = std::int32_t{0}; // Index in the UTF-8 string + auto const length = static_cast(lhs.length()); + + auto inserter = std::inserter(rhs, std::begin(rhs)); + while (i < length) { + UChar32 ch; + U8_NEXT(lhs.data(), i, length, ch); + if (ch < 0) { + throw std::runtime_error{"Invalid UTF-8 sequence"}; + } + *inserter = Glyph{.symbol = static_cast(ch)}; } - return glyphs | attr; + return rhs; +} + +template +[[nodiscard]] auto operator+(T lhs, std::u32string_view rhs) -> T +{ + return lhs += rhs; +} + +template +[[nodiscard]] auto operator+(std::u32string_view lhs, T rhs) -> T +{ + rhs.reserve(rhs.size() + lhs.size()); + std::ranges::transform(lhs, std::inserter(rhs, std::begin(rhs)), + [](auto ch) { return Glyph{.symbol = ch}; }); + return rhs; +} + +template +[[nodiscard]] auto operator+(T lhs, U const& rhs) -> T +{ + lhs.reserve(lhs.size() + rhs.size()); + std::ranges::copy(rhs, std::inserter(lhs, std::end(lhs))); + return lhs; +} + +template +[[nodiscard]] auto operator+(T lhs, Glyph const& rhs) -> T +{ + lhs.insert(std::end(lhs), rhs); + return lhs; +} + +template +[[nodiscard]] auto operator+(Glyph const& lhs, T rhs) -> T +{ + rhs.insert(std::begin(rhs), lhs); + return rhs; +} + +[[nodiscard]] inline auto operator+(Glyph const& lhs, std::string_view rhs) + -> std::vector +{ + return lhs + detail::utf8_to_glyphs(rhs); +} + +[[nodiscard]] inline auto operator+(Glyph const& lhs, std::u32string_view rhs) + -> std::vector +{ + return lhs + detail::utf32_to_glyphs(rhs); +} + +[[nodiscard]] inline auto operator+(std::string_view lhs, Glyph const& rhs) + -> std::vector +{ + return detail::utf8_to_glyphs(lhs) + rhs; +} + +[[nodiscard]] inline auto operator+(std::u32string_view lhs, Glyph const& rhs) + -> std::vector +{ + return detail::utf32_to_glyphs(lhs) + rhs; } } // namespace esc \ No newline at end of file diff --git a/tests/glyph.test.cpp b/tests/glyph.test.cpp index c70d5d2..694b67d 100644 --- a/tests/glyph.test.cpp +++ b/tests/glyph.test.cpp @@ -422,4 +422,404 @@ TEST_CASE("Operator|(StringType) with multibyte symbols", "[GlyphString]") REQUIRE(gs[6] == Glyph{U'界', {.background = ColorIndex::Green}}); REQUIRE(gs[7] == Glyph{U'𐍈', {.background = ColorIndex::Green}}); } +} + +TEST_CASE("Chained pipe ops") +{ + SECTION("Glyph") + { + auto const g = Glyph{U'a'} | bg(ColorIndex::Green) | + fg(ColorIndex::Red) | Trait::Bold | Trait::Italic | + remove_trait(Trait::Bold); + REQUIRE(g == Glyph{.symbol = U'a', + .brush = {.background = ColorIndex::Green, + .foreground = ColorIndex::Red, + .traits = Trait::Italic}}); + } + + SECTION("GlyphString") + { + auto const gs = "abc" | bg(ColorIndex::Green) | fg(ColorIndex::Red) | + Trait::Bold | Trait::Italic | remove_trait(Trait::Bold); + + REQUIRE(gs.size() == 3); + REQUIRE(gs[0] == Glyph{.symbol = U'a', + .brush = {.background = ColorIndex::Green, + .foreground = ColorIndex::Red, + .traits = Trait::Italic}}); + REQUIRE(gs[1] == Glyph{.symbol = U'b', + .brush = {.background = ColorIndex::Green, + .foreground = ColorIndex::Red, + .traits = Trait::Italic}}); + REQUIRE(gs[2] == Glyph{.symbol = U'c', + .brush = {.background = ColorIndex::Green, + .foreground = ColorIndex::Red, + .traits = Trait::Italic}}); + } +} + +TEST_CASE("operator+=(GlyphString, ...)", "[Glyph]") +{ + SECTION("GS += GS") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += "def" | bg(ColorIndex::Red); + + REQUIRE(gs.size() == 6); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd', {.background = ColorIndex::Red}}); + REQUIRE(gs[4] == Glyph{U'e', {.background = ColorIndex::Red}}); + REQUIRE(gs[5] == Glyph{U'f', {.background = ColorIndex::Red}}); + } + + SECTION("GS += std::u32string_view") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += U"d😀f"; + + REQUIRE(gs.size() == 6); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + REQUIRE(gs[4] == Glyph{U'😀'}); + REQUIRE(gs[5] == Glyph{U'f'}); + } + + SECTION("GS += std::string_view") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += "d😀f"; + + REQUIRE(gs.size() == 6); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + REQUIRE(gs[4] == Glyph{U'😀'}); + REQUIRE(gs[5] == Glyph{U'f'}); + } + + SECTION("GS += Glyph") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += Glyph{U'd', {.background = ColorIndex::Red}}; + + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd', {.background = ColorIndex::Red}}); + } + + SECTION("GS += Character") + { + SECTION("char") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += 'd'; + + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + + SECTION("char8_t") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += u8'd'; + + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + + SECTION("char16_t") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += u'd'; + + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + + SECTION("char32_t") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += U'd'; + + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + + SECTION("signed char") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += static_cast('d'); + + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + + SECTION("unsigned char") + { + auto gs = "abc" | bg(ColorIndex::Green); + gs += static_cast('d'); + + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + } +} + +TEST_CASE("operator+(..., ...)", "[Glyph]") +{ + SECTION("Glyph + Glyph") + { + auto const gs = Glyph{U'a'} + Glyph{U'b'}; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("Glyph + Character", "[Glyph]") + { + SECTION("char") + { + auto const gs = Glyph{U'a'} + 'b'; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("char8_t") + { + auto const gs = Glyph{U'a'} + u8'b'; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("char16_t") + { + auto const gs = Glyph{U'a'} + u'b'; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("char32_t") + { + auto const gs = Glyph{U'a'} + U'b'; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("signed char") + { + auto const gs = Glyph{U'a'} + static_cast('b'); + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("unsigned char") + { + auto const gs = Glyph{U'a'} + static_cast('b'); + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + } + + SECTION("Character + Glyph", "[Glyph]") + { + SECTION("char") + { + auto const gs = 'a' + Glyph{U'b'}; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("char8_t") + { + auto const gs = u8'a' + Glyph{U'b'}; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("char16_t") + { + auto const gs = u'a' + Glyph{U'b'}; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("char32_t") + { + auto const gs = U'a' + Glyph{U'b'}; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("signed char") + { + auto const gs = static_cast('a') + Glyph{U'b'}; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + + SECTION("unsigned char") + { + auto const gs = static_cast('a') + Glyph{U'b'}; + REQUIRE(gs.size() == 2); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + } + } + + SECTION("GlyphString + std::u32string_view") + { + auto const gs = ("abc" | bg(ColorIndex::Green)) + U"d😀f"; + REQUIRE(gs.size() == 6); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + REQUIRE(gs[4] == Glyph{U'😀'}); + REQUIRE(gs[5] == Glyph{U'f'}); + } + + SECTION("std::u32string_view + GlyphString") + { + auto const gs = U"a😀c" + ("def" | bg(ColorIndex::Green)); + REQUIRE(gs.size() == 6); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'😀'}); + REQUIRE(gs[2] == Glyph{U'c'}); + REQUIRE(gs[3] == Glyph{U'd', {.background = ColorIndex::Green}}); + REQUIRE(gs[4] == Glyph{U'e', {.background = ColorIndex::Green}}); + REQUIRE(gs[5] == Glyph{U'f', {.background = ColorIndex::Green}}); + } + + SECTION("GlyphString + std::string_view") + { + auto const gs = ("abc" | bg(ColorIndex::Green)) + "d😀f"; + REQUIRE(gs.size() == 6); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + REQUIRE(gs[4] == Glyph{U'😀'}); + REQUIRE(gs[5] == Glyph{U'f'}); + } + + SECTION("std::string_view + GlyphString") + { + auto const gs = "a😀c" + ("def" | bg(ColorIndex::Green)); + REQUIRE(gs.size() == 6); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'😀'}); + REQUIRE(gs[2] == Glyph{U'c'}); + REQUIRE(gs[3] == Glyph{U'd', {.background = ColorIndex::Green}}); + REQUIRE(gs[4] == Glyph{U'e', {.background = ColorIndex::Green}}); + REQUIRE(gs[5] == Glyph{U'f', {.background = ColorIndex::Green}}); + } + + SECTION("GlyphString + GlyphString") + { + auto const gs = + ("abc" | bg(ColorIndex::Green)) + ("d😀f" | bg(ColorIndex::Red)); + REQUIRE(gs.size() == 6); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd', {.background = ColorIndex::Red}}); + REQUIRE(gs[4] == Glyph{U'😀', {.background = ColorIndex::Red}}); + REQUIRE(gs[5] == Glyph{U'f', {.background = ColorIndex::Red}}); + } + + SECTION("GlyphString + Glyph") + { + auto const gs = ("abc" | bg(ColorIndex::Green)) + Glyph{U'd'}; + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a', {.background = ColorIndex::Green}}); + REQUIRE(gs[1] == Glyph{U'b', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'c', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + + SECTION("Glyph + GlyphString") + { + auto const gs = Glyph{U'a'} + ("def" | bg(ColorIndex::Green)); + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'd', {.background = ColorIndex::Green}}); + REQUIRE(gs[2] == Glyph{U'e', {.background = ColorIndex::Green}}); + REQUIRE(gs[3] == Glyph{U'f', {.background = ColorIndex::Green}}); + } + + SECTION("Glyph + std::string_view") + { + auto const gs = Glyph{U'a'} + "b😀d"; + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + REQUIRE(gs[2] == Glyph{U'😀'}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + + SECTION("Glyph + std::u32string_view") + { + auto const gs = Glyph{U'a'} + U"b😀d"; + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'b'}); + REQUIRE(gs[2] == Glyph{U'😀'}); + REQUIRE(gs[3] == Glyph{U'd'}); + } + + SECTION("std::string_view + Glyph") + { + auto const gs = "a😀b" + Glyph{U'c'}; + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'😀'}); + REQUIRE(gs[2] == Glyph{U'b'}); + REQUIRE(gs[3] == Glyph{U'c'}); + } + + SECTION("std::u32string_view + Glyph") + { + auto const gs = U"a😀b" + Glyph{U'c'}; + REQUIRE(gs.size() == 4); + REQUIRE(gs[0] == Glyph{U'a'}); + REQUIRE(gs[1] == Glyph{U'😀'}); + REQUIRE(gs[2] == Glyph{U'b'}); + REQUIRE(gs[3] == Glyph{U'c'}); + } } \ No newline at end of file