diff --git a/outputview/outputmodel.cpp b/outputview/outputmodel.cpp --- a/outputview/outputmodel.cpp +++ b/outputview/outputmodel.cpp @@ -27,14 +27,16 @@ #include #include +#include #include #include #include #include #include #include +#include #include Q_DECLARE_METATYPE(QVector) @@ -63,7 +65,10 @@ public: ParseWorker() : QObject(0) - , m_filter( new NoFilterStrategy ) + , m_filter(new NoFilterStrategy) + , m_preFilterFunctions { + &KDevelop::stripAnsiSequences + } , m_timer(new QTimer(this)) { m_timer->setInterval(BATCH_AGGREGATE_TIME_DELAY); @@ -110,6 +115,14 @@ QVector filteredItems; filteredItems.reserve(qMin(BATCH_SIZE, m_cachedLines.size())); + // apply pre-filtering functions + foreach (const auto filterFunction, m_preFilterFunctions) { + std::for_each(m_cachedLines.begin(), m_cachedLines.end(), [filterFunction](QString& in) { + in = filterFunction(in); + }); + } + + // apply filtering strategy foreach(const QString& line, m_cachedLines) { FilteredItem item = m_filter->errorInLine(line); if( item.type == FilteredItem::InvalidItem ) { @@ -134,6 +147,7 @@ private: QSharedPointer m_filter; + QVector> m_preFilterFunctions; QStringList m_cachedLines; QTimer* m_timer; diff --git a/outputview/tests/test_filteringstrategy.h b/outputview/tests/test_filteringstrategy.h --- a/outputview/tests/test_filteringstrategy.h +++ b/outputview/tests/test_filteringstrategy.h @@ -41,6 +41,8 @@ void testScriptErrorFilterStrategy(); void testNativeAppErrorFilterStrategy_data(); void testNativeAppErrorFilterStrategy(); + void testAnsiFilterStrategy_data(); + void testAnsiFilterStrategy(); void testStaticAnalysisFilterStrategy_data(); void testStaticAnalysisFilterStrategy(); void testExtractionOfLineAndColumn_data(); diff --git a/util/kdevstringhandler.h b/util/kdevstringhandler.h --- a/util/kdevstringhandler.h +++ b/util/kdevstringhandler.h @@ -60,6 +60,12 @@ * @return String no longer containing any HTML tags */ KDEVPLATFORMUTIL_EXPORT QString htmlToPlainText(const QString& s, HtmlToPlainTextMode mode = FastMode); + + /** + * Strip ANSI sequences from string @p str + */ + KDEVPLATFORMUTIL_EXPORT QString stripAnsiSequences(const QString& str); + } #endif // KDEVPLATFORM_KDEVSTRINGHANDLER_H diff --git a/util/kdevstringhandler.cpp b/util/kdevstringhandler.cpp --- a/util/kdevstringhandler.cpp +++ b/util/kdevstringhandler.cpp @@ -166,3 +166,66 @@ } } +QString KDevelop::stripAnsiSequences(const QString& str) +{ + if (str.isEmpty()) { + return QString(); // fast path + } + + enum { + PLAIN, + ANSI_START, + ANSI_CSI, + ANSI_SEQUENCE, + ANSI_WAITING_FOR_ST, + ANSI_ST_STARTED + } state = PLAIN; + + QString result; + result.reserve(str.count()); + + foreach (const QChar c, str) { + const auto val = c.unicode(); + switch (state) { + case PLAIN: + if (val == 27) // 'ESC' + state = ANSI_START; + else if (val == 155) // equivalent to 'ESC'-'[' + state = ANSI_CSI; + else + result.append(c); + break; + case ANSI_START: + if (val == 91) // [ + state = ANSI_CSI; + else if (val == 80 || val == 93 || val == 94 || val == 95) // 'P', ']', '^' and '_' + state = ANSI_WAITING_FOR_ST; + else if (val >= 64 && val <= 95) + state = PLAIN; + else + state = ANSI_SEQUENCE; + break; + case ANSI_CSI: + if (val >= 64 && val <= 126) // Anything between '@' and '~' + state = PLAIN; + break; + case ANSI_SEQUENCE: + if (val >= 64 && val <= 95) // Anything between '@' and '_' + state = PLAIN; + break; + case ANSI_WAITING_FOR_ST: + if (val == 7) // 'BEL' + state = PLAIN; + if (val == 27) // 'ESC' + state = ANSI_ST_STARTED; + break; + case ANSI_ST_STARTED: + if (val == 92) // '\' + state = PLAIN; + else + state = ANSI_WAITING_FOR_ST; + break; + } + } + return result; +} diff --git a/util/tests/test_stringhandler.h b/util/tests/test_stringhandler.h --- a/util/tests/test_stringhandler.h +++ b/util/tests/test_stringhandler.h @@ -32,6 +32,8 @@ void testHtmlToPlainText(); void testHtmlToPlainText_data(); + void testStripAnsiSequences(); + void testStripAnsiSequences_data(); }; #endif // TESTSTRINGHANDLER_H diff --git a/util/tests/test_stringhandler.cpp b/util/tests/test_stringhandler.cpp --- a/util/tests/test_stringhandler.cpp +++ b/util/tests/test_stringhandler.cpp @@ -54,3 +54,22 @@ << "

bar()

a
foo
" << KDevelop::CompleteMode << "bar() \na\nfoo"; } + +void TestStringHandler::testStripAnsiSequences() +{ + QFETCH(QString, input); + QFETCH(QString, expectedOutput); + + const auto output = stripAnsiSequences(input); + QCOMPARE(output, expectedOutput); +} + +void TestStringHandler::testStripAnsiSequences_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expectedOutput"); + + QTest::newRow("simple") + << QStringLiteral("foo bar:") + << "foo bar:"; +} \ No newline at end of file