//===-- AnsiTerminalTest.cpp ----------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "gtest/gtest.h" #include "lldb/Utility/AnsiTerminal.h" #include "lldb/Utility/StreamString.h" using namespace lldb_private; TEST(AnsiTerminal, Empty) { EXPECT_EQ("", ansi::FormatAnsiTerminalCodes("")); } TEST(AnsiTerminal, WhiteSpace) { EXPECT_EQ(" ", ansi::FormatAnsiTerminalCodes(" ")); EXPECT_EQ(" ", ansi::StripAnsiTerminalCodes(" ")); } TEST(AnsiTerminal, AtEnd) { EXPECT_EQ("abc\x1B[30m", ansi::FormatAnsiTerminalCodes("abc${ansi.fg.black}")); EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("abc\x1B[30m")); } TEST(AnsiTerminal, AtStart) { EXPECT_EQ("\x1B[30mabc", ansi::FormatAnsiTerminalCodes("${ansi.fg.black}abc")); EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("\x1B[30mabc")); } TEST(AnsiTerminal, KnownPrefix) { EXPECT_EQ("${ansi.fg.redish}abc", ansi::FormatAnsiTerminalCodes("${ansi.fg.redish}abc")); } TEST(AnsiTerminal, Unknown) { EXPECT_EQ("${ansi.fg.foo}abc", ansi::FormatAnsiTerminalCodes("${ansi.fg.foo}abc")); } TEST(AnsiTerminal, Incomplete) { EXPECT_EQ("abc${ansi.", ansi::FormatAnsiTerminalCodes("abc${ansi.")); } TEST(AnsiTerminal, Twice) { EXPECT_EQ("\x1B[30m\x1B[31mabc", ansi::FormatAnsiTerminalCodes("${ansi.fg.black}${ansi.fg.red}abc")); EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("\x1B[30m\x1B[31mabc")); } TEST(AnsiTerminal, Basic) { EXPECT_EQ( "abc\x1B[31mabc\x1B[0mabc", ansi::FormatAnsiTerminalCodes("abc${ansi.fg.red}abc${ansi.normal}abc")); EXPECT_EQ("abcabcabc", ansi::StripAnsiTerminalCodes("abc\x1B[31mabc\x1B[0mabc")); } TEST(AnsiTerminal, InvalidEscapeCode) { EXPECT_EQ("abc\x1B[31kabcabc", ansi::StripAnsiTerminalCodes("abc\x1B[31kabc\x1B[0mabc")); } TEST(AnsiTerminal, FindNextAnsiSequenceBasic) { auto [left, escape, right] = ansi::FindNextAnsiSequence("foo\x1B[31mbar"); EXPECT_EQ("foo", left); EXPECT_EQ("\x1B[31m", escape); EXPECT_EQ("bar", right); } TEST(AnsiTerminal, FindNextAnsiSequenceIncompleteStart) { auto [left, escape, right] = ansi::FindNextAnsiSequence("foo\x1B[bar\x1B[31mbaz"); EXPECT_EQ("foo\x1B[bar", left); EXPECT_EQ("\x1B[31m", escape); EXPECT_EQ("baz", right); } TEST(AnsiTerminal, FindNextAnsiSequenceEscapeStart) { auto [left, escape, right] = ansi::FindNextAnsiSequence("\x1B[31mfoo"); EXPECT_EQ("", left); EXPECT_EQ("\x1B[31m", escape); EXPECT_EQ("foo", right); } TEST(AnsiTerminal, TrimAndPad) { // Test basic ASCII. EXPECT_EQ(" ", ansi::TrimAndPad("", 5)); EXPECT_EQ("foo ", ansi::TrimAndPad("foo", 5)); EXPECT_EQ("fooba", ansi::TrimAndPad("fooba", 5)); EXPECT_EQ("fooba", ansi::TrimAndPad("foobar", 5)); // Simple test that ANSI escape codes don't contribute to the visible width. EXPECT_EQ("\x1B[30m ", ansi::TrimAndPad("\x1B[30m", 5)); EXPECT_EQ("\x1B[30mfoo ", ansi::TrimAndPad("\x1B[30mfoo", 5)); EXPECT_EQ("\x1B[30mfooba", ansi::TrimAndPad("\x1B[30mfooba", 5)); EXPECT_EQ("\x1B[30mfooba", ansi::TrimAndPad("\x1B[30mfoobar", 5)); // Test that we include as many escape codes as we can. EXPECT_EQ("fooba\x1B[30m", ansi::TrimAndPad("fooba\x1B[30m", 5)); EXPECT_EQ("fooba\x1B[30m\x1B[34m", ansi::TrimAndPad("fooba\x1B[30m\x1B[34m", 5)); EXPECT_EQ("fooba\x1B[30m\x1B[34m", ansi::TrimAndPad("fooba\x1B[30m\x1B[34mr", 5)); // Test Unicode. EXPECT_EQ("❤️ ", ansi::TrimAndPad("❤️", 5)); EXPECT_EQ(" ❤️", ansi::TrimAndPad(" ❤️", 5)); EXPECT_EQ("12❤️4❤️", ansi::TrimAndPad("12❤️4❤️", 5)); EXPECT_EQ("12❤️45", ansi::TrimAndPad("12❤️45❤️", 5)); // This string previously triggered a bug in handling incomplete Unicode // characters, when we had already accumulated some previous parts of the // string. const char *quick = "The \x1B[0mquick\x1B[0m 💨\x1B[0m"; EXPECT_EQ(ansi::TrimAndPad(quick, 0), ""); EXPECT_EQ(ansi::TrimAndPad(quick, 9), "The \x1B[0mquick\x1B[0m"); EXPECT_EQ(ansi::TrimAndPad(quick, 10), "The \x1B[0mquick\x1B[0m "); // The emoji is 2 columns, so 11 is not quite enough. EXPECT_EQ(ansi::TrimAndPad(quick, 11, '_'), "The \x1B[0mquick\x1B[0m _"); // 12 exactly enough to include the emoji and proceeding ANSI code. EXPECT_EQ(ansi::TrimAndPad(quick, 12), quick); } TEST(AnsiTerminal, TrimAtWordBoundary) { // Nothing in, nothing out. EXPECT_EQ(ansi::TrimAtWordBoundary("", 0), ""); EXPECT_EQ(ansi::TrimAtWordBoundary("", 1), ""); EXPECT_EQ(ansi::TrimAtWordBoundary("", 1), ""); // All whitespace, return nothing. EXPECT_EQ(ansi::TrimAtWordBoundary(" ", 1), ""); // Leading and trailing whitespace are removed. EXPECT_EQ(ansi::TrimAtWordBoundary(" ab ", 0), "ab"); EXPECT_EQ(ansi::TrimAtWordBoundary(" ab ", 5), "ab"); EXPECT_EQ(ansi::TrimAtWordBoundary(" 🦊🦊 ", 0), "🦊🦊"); EXPECT_EQ(ansi::TrimAtWordBoundary(" 🦊🦊 ", 5), "🦊🦊"); // When it is a single word, we ignore the max columns and return the word. EXPECT_EQ(ansi::TrimAtWordBoundary("abc", 0), "abc"); EXPECT_EQ(ansi::TrimAtWordBoundary("abc", 1), "abc"); EXPECT_EQ(ansi::TrimAtWordBoundary("abc", 2), "abc"); EXPECT_EQ(ansi::TrimAtWordBoundary("abc", 3), "abc"); EXPECT_EQ(ansi::TrimAtWordBoundary("abc", 4), "abc"); EXPECT_EQ(ansi::TrimAtWordBoundary("abcdefghij", 2), "abcdefghij"); EXPECT_EQ(ansi::TrimAtWordBoundary("🦊🦊", 0), "🦊🦊"); EXPECT_EQ(ansi::TrimAtWordBoundary("🦊🦊", 4), "🦊🦊"); // If it fits, return the entire word. EXPECT_EQ(ansi::TrimAtWordBoundary("abc", 5), "abc"); EXPECT_EQ(ansi::TrimAtWordBoundary("🦊🦊", 5), "🦊🦊"); // ANSI codes do not add to width. EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0m", 0), "\x1B[0m"); // Preceding ANSI codes are included. EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0mab cd", 2), "\x1B[0mab"); EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0m🦊 🐱", 2), "\x1B[0m🦊"); EXPECT_EQ(ansi::TrimAtWordBoundary("🦊\x1B[0m\x1B[0m🐱 🐈", 4), "🦊\x1B[0m\x1B[0m🐱"); // If there's more than one, include all of them. EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0m\x1B[0m\x1B[0mab cd", 2), "\x1B[0m\x1B[0m\x1B[0mab"); // Proceeding ANSI codes are included. EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0mab\x1B[0m cd", 2), "\x1B[0mab\x1B[0m"); EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0mab\x1B[0m", 4), "\x1B[0mab\x1B[0m"); EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0m🦊\x1B[0m 🐱", 2), "\x1B[0m🦊\x1B[0m"); EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0m🦊\x1B[0m", 4), "\x1B[0m🦊\x1B[0m"); // Include all if more than one. EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0mab\x1B[0m\x1B[0m\x1B[0m cd", 2), "\x1B[0mab\x1B[0m\x1B[0m\x1B[0m"); // Mutliple pre and proceding ANSI codes. EXPECT_EQ(ansi::TrimAtWordBoundary("\x1B[0m\x1B[0mab\x1B[0m\x1B[0m cd", 2), "\x1B[0m\x1B[0mab\x1B[0m\x1B[0m"); // When multiple words fit, include as many as we can while still ending on // a word boundary. const char *fox_ascii = "The quick brown fox jumped."; // Can't fit one word, just returns first word. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, 0), "The"); // Exactly 3 is required for one word. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, 3), "The"); // Exactly 9 is required to fit 2 words. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, 9), "The quick"); // So anything less than 9 is just one word. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, 8), "The"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, 4), "The"); // 3 words is exactly 15. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, 15), "The quick brown"); // Anything less is 2 words. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, 14), "The quick"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, 10), "The quick"); // The whole string. size_t fox_ascii_len = strlen(fox_ascii); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, fox_ascii_len), fox_ascii); // Anything less and we remove the last word. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_ascii, fox_ascii_len - 1), "The quick brown fox"); // Width calculation is Unicode aware and a run of Unicode is a word just // like a run of ASCII is. // Note that these emoji avoid any compound emoji where there are // non-printable modifiers. This is because llvm::sys::locale::columnWidth // returns -1 for these non-printable adjustment characters. At this time, // TrimAtWordBoundary simply cannot handle them well. const char *fox_unicode = "🦊 💨🟤 🔼"; // Emoji have width 2, so this "word" would not fit so we just return it. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_unicode, 0), "🦊"); // It does fit width 2. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_unicode, 2), "🦊"); // Need 7 to fit 2 words. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_unicode, 7), "🦊 💨🟤"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_unicode, 6), "🦊"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_unicode, 4), "🦊"); // The entire string. size_t fox_unicode_len = llvm::sys::locale::columnWidth(fox_unicode); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_unicode, fox_unicode_len), "🦊 💨🟤 🔼"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_unicode, fox_unicode_len - 1), "🦊 💨🟤"); const char *fox_everything = "The \x1B[0mquick\x1B[0m 💨\x1B[0m brown \x1B[0m🟤 fox🦊 🔼jumped."; EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, 0), "The"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, 3), "The"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, 6), "The"); // Exactly 9 to fit two words. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, 9), "The \x1B[0mquick\x1B[0m"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, 10), "The \x1B[0mquick\x1B[0m"); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, 11), "The \x1B[0mquick\x1B[0m"); // <2 wide emoji> adds 3 more to get to 12. EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, 12), "The \x1B[0mquick\x1B[0m 💨\x1B[0m"); // The entire string. We use the ansi:: width function here because it strips // ANSI codes that llvm::sys::locale's function cannot cope with. size_t fox_everything_len = ansi::ColumnWidth(fox_everything); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, fox_everything_len), fox_everything); EXPECT_EQ(ansi::TrimAtWordBoundary(fox_everything, fox_everything_len - 1), "The \x1B[0mquick\x1B[0m 💨\x1B[0m brown \x1B[0m🟤 fox🦊"); } static void TestLines(const std::string &input, int indent, uint32_t output_max_columns, const llvm::StringRef &expected) { StreamString strm; strm.SetIndentLevel(indent); ansi::OutputWordWrappedLines(strm, input, output_max_columns, /*use_color=*/false); EXPECT_EQ(expected, strm.GetString()); } TEST(AnsiTerminal, OutputWordWrappedLines) { // Nothing in, nothing out. No newline, no indent. TestLines("", 0, 5, ""); TestLines("", 5, 5, ""); // A single line will have a newline on the end. TestLines("abc", 0, 1, "abc\n"); TestLines("abc", 2, 5, " abc\n"); TestLines("🦊🦊", 0, 0, "🦊🦊\n"); TestLines("🦊🦊", 0, 2, "🦊🦊\n"); // If the indent uses up all the columns, print the word on the same line // anyway. This prevents us outputting indent only lines forever. TestLines("abcdefghij", 4, 2, " abcdefghij\n"); // Leading whitespace is ignored because we're going to indent using the // stream. TestLines(" ", 3, 10, ""); TestLines(" abc", 0, 4, "abc\n"); TestLines(" abc", 2, 6, " abc\n"); // Multiple lines. Each one ends with a newline. TestLines("abc def", 0, 4, "abc\ndef\n"); TestLines("abc def", 0, 5, "abc\ndef\n"); // Indent applied to each line. TestLines("abc def", 2, 4, " abc\n def\n"); // First word is wider than a whole line, do not split that word. TestLines("aabbcc ddee", 0, 5, "aabbcc\nddee\n"); const char *fox_str = "The quick brown fox."; TestLines(fox_str, 0, 30, "The quick brown fox.\n"); TestLines(fox_str, 5, 30, " The quick brown fox.\n"); TestLines(fox_str, 2, 15, " The quick\n brown fox.\n"); // Must remove the spaces from the end of the first line. TestLines("The quick brown fox.", 0, 15, "The quick\nbrown fox.\n"); // If ANSI formatting is applied to multiple words, that range of words may // be split over multiple lines. StreamString indented_strm; indented_strm.SetIndentLevel(2); ansi::OutputWordWrappedLines(indented_strm, "\x1B[4mabc def\x1B[0m ghi", 6, /*use_color=*/false); // The two spaces before "def" would have the previous ANSI code applied to // them. EXPECT_EQ(" \x1B[4mabc\n def\x1B[0m\n ghi\n", indented_strm.GetString()); // If we can emit ANSI, we can use cursor positions to skip forward, // which leaves the indent unformatted. // (in normal use the inputs are command descriptions, which already have // ANSI removed if the terminal does not support it) indented_strm.Clear(); ansi::OutputWordWrappedLines(indented_strm, "\x1B[4mabc def\x1B[0m ghi", 6, /*use_color=*/true); EXPECT_EQ("\x1B[2C\x1B[4mabc\n\x1B[2Cdef\x1B[0m\n\x1B[2Cghi\n", indented_strm.GetString()); }