diff --git a/src/libs/utils/ansiescapecodehandler.cpp b/src/libs/utils/ansiescapecodehandler.cpp index bc7e6dcb1a4..d956c90f416 100644 --- a/src/libs/utils/ansiescapecodehandler.cpp +++ b/src/libs/utils/ansiescapecodehandler.cpp @@ -86,53 +86,83 @@ QList AnsiEscapeCodeHandler::parseText(const FormattedText &input DefaultBackgroundColor = 49 }; - QList outputData; - - QTextCharFormat charFormat = m_previousFormatClosed ? input.format : m_previousFormat; - const QString escape = QLatin1String("\x1b["); - const int escapePos = input.text.indexOf(escape); - if (escapePos < 0) { - outputData << FormattedText(input.text, charFormat); - return outputData; - } else if (escapePos != 0) { - outputData << FormattedText(input.text.left(escapePos), charFormat); - } - const QChar semicolon = QLatin1Char(';'); const QChar colorTerminator = QLatin1Char('m'); const QChar eraseToEol = QLatin1Char('K'); - // strippedText always starts with "\e[" - QString strippedText = input.text.mid(escapePos); + + QList outputData; + QTextCharFormat charFormat = m_previousFormatClosed ? input.format : m_previousFormat; + QString strippedText; + if (m_pendingText.isEmpty()) { + strippedText = input.text; + } else { + strippedText = m_pendingText.append(input.text); + m_pendingText.clear(); + } + while (!strippedText.isEmpty()) { - while (strippedText.startsWith(escape)) { - strippedText.remove(0, 2); + QTC_ASSERT(m_pendingText.isEmpty(), break); + const int escapePos = strippedText.indexOf(escape[0]); + if (escapePos < 0) { + outputData << FormattedText(strippedText, charFormat); + break; + } else if (escapePos != 0) { + outputData << FormattedText(strippedText.left(escapePos), charFormat); + strippedText.remove(0, escapePos); + } + QTC_ASSERT(strippedText[0] == escape[0], break); + + while (!strippedText.isEmpty() && escape[0] == strippedText[0]) { + if (escape.startsWith(strippedText)) { + // control secquence is not complete + m_pendingText += strippedText; + strippedText.clear(); + break; + } + if (!strippedText.startsWith(escape)) { + // not a control sequence + m_pendingText.clear(); + outputData << FormattedText(strippedText.left(1), charFormat); + strippedText.remove(0, 1); + continue; + } + m_pendingText += strippedText.mid(0, escape.length()); + strippedText.remove(0, escape.length()); // \e[K is not supported. Just strip it. if (strippedText.startsWith(eraseToEol)) { + m_pendingText.clear(); strippedText.remove(0, 1); continue; } // get the number QString strNumber; QStringList numbers; - while (strippedText.at(0).isDigit() || strippedText.at(0) == semicolon) { + while (!strippedText.isEmpty()) { if (strippedText.at(0).isDigit()) { strNumber += strippedText.at(0); } else { - numbers << strNumber; + if (!strNumber.isEmpty()) + numbers << strNumber; + if (strNumber.isEmpty() || strippedText.at(0) != semicolon) + break; strNumber.clear(); } + m_pendingText += strippedText.mid(0, 1); strippedText.remove(0, 1); } - if (!strNumber.isEmpty()) - numbers << strNumber; + if (strippedText.isEmpty()) + break; // remove terminating char if (!strippedText.startsWith(colorTerminator)) { + m_pendingText.clear(); strippedText.remove(0, 1); - continue; + break; } + // got consistent control sequence, ok to clear pending text + m_pendingText.clear(); strippedText.remove(0, 1); if (numbers.isEmpty()) { @@ -224,17 +254,6 @@ QList AnsiEscapeCodeHandler::parseText(const FormattedText &input } } } - - if (strippedText.isEmpty()) - break; - int index = strippedText.indexOf(escape); - if (index > 0) { - outputData << FormattedText(strippedText.left(index), charFormat); - strippedText.remove(0, index); - } else if (index == -1) { - outputData << FormattedText(strippedText, charFormat); - break; - } } return outputData; } diff --git a/src/libs/utils/ansiescapecodehandler.h b/src/libs/utils/ansiescapecodehandler.h index f1f75817859..34d7593f060 100644 --- a/src/libs/utils/ansiescapecodehandler.h +++ b/src/libs/utils/ansiescapecodehandler.h @@ -62,6 +62,7 @@ private: bool m_previousFormatClosed; QTextCharFormat m_previousFormat; + QString m_pendingText; }; } // namespace Utils diff --git a/tests/auto/utils/ansiescapecodehandler/tst_ansiescapecodehandler.cpp b/tests/auto/utils/ansiescapecodehandler/tst_ansiescapecodehandler.cpp index 16675bb885b..9825758cfed 100644 --- a/tests/auto/utils/ansiescapecodehandler/tst_ansiescapecodehandler.cpp +++ b/tests/auto/utils/ansiescapecodehandler/tst_ansiescapecodehandler.cpp @@ -57,6 +57,7 @@ private Q_SLOTS: void testSimpleFormat(); void testSimpleFormat_data(); void testLineOverlappingFormat(); + void testSplitControlSequence(); private: const QString red; @@ -225,6 +226,31 @@ void tst_AnsiEscapeCodeHandler::testSimpleFormat_data() << (FormattedTextList() << FormattedText("All text after this is ", defaultFormat) << FormattedText("not deleted", defaultFormat)); + + QTest::newRow("Unfinished control sequence \\x1b") + << QString("A text before \x1b") << QTextCharFormat() + << (FormattedTextList() + << FormattedText("A text before ", defaultFormat)); + + QTest::newRow("Unfinished control sequence \\x1b[") + << QString("A text before \x1b[") << QTextCharFormat() + << (FormattedTextList() + << FormattedText("A text before ", defaultFormat)); + + QTest::newRow("Unfinished control sequence \\x1b[3") + << QString("A text before \x1b[3") << QTextCharFormat() + << (FormattedTextList() + << FormattedText("A text before ", defaultFormat)); + + QTest::newRow("Unfinished control sequence \\x1b[31") + << QString("A text before \x1b[31") << QTextCharFormat() + << (FormattedTextList() + << FormattedText("A text before ", defaultFormat)); + + QTest::newRow("Unfinished control sequence \\x1b[31,") + << QString("A text before \x1b[31,") << QTextCharFormat() + << (FormattedTextList() + << FormattedText("A text before ", defaultFormat)); } void tst_AnsiEscapeCodeHandler::testLineOverlappingFormat() @@ -254,6 +280,29 @@ void tst_AnsiEscapeCodeHandler::testLineOverlappingFormat() QCOMPARE(result[3].format, defaultFormat); } +void tst_AnsiEscapeCodeHandler::testSplitControlSequence() +{ + // Test line-overlapping formats + const QString line1 = "Normal line \x1b"; + const QString line2 = "[1m bold line"; + + QTextCharFormat defaultFormat; + + AnsiEscapeCodeHandler handler; + FormattedTextList result; + result.append(handler.parseText(FormattedText(line1, defaultFormat))); + result.append(handler.parseText(FormattedText(line2, defaultFormat))); + + QTextCharFormat boldFormat; + boldFormat.setFontWeight(QFont::Bold); + + QCOMPARE(result.size(), 2); + QCOMPARE(result[0].text, QLatin1String("Normal line ")); + QCOMPARE(result[0].format, defaultFormat); + QCOMPARE(result[1].text, QLatin1String(" bold line")); + QCOMPARE(result[1].format, boldFormat); +} + QTEST_APPLESS_MAIN(tst_AnsiEscapeCodeHandler) #include "tst_ansiescapecodehandler.moc"