diff --git a/outputview/CMakeLists.txt b/outputview/CMakeLists.txt --- a/outputview/CMakeLists.txt +++ b/outputview/CMakeLists.txt @@ -24,6 +24,7 @@ filtereditem.h outputmodel.h outputdelegate.h + outputfilteringstrategies.h ioutputviewmodel.h ifilterstrategy.h outputjob.h diff --git a/outputview/ifilterstrategy.h b/outputview/ifilterstrategy.h --- a/outputview/ifilterstrategy.h +++ b/outputview/ifilterstrategy.h @@ -1,6 +1,7 @@ /* Interface description for KDevelop OutputView Filter strategies Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com + Copyright (C) 2016 Kevin Funk This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,7 +23,8 @@ #include "outputviewexport.h" -class QString; +#include +#include namespace KDevelop { @@ -38,10 +40,15 @@ class KDEVPLATFORMOUTPUTVIEW_EXPORT IFilterStrategy { public: - IFilterStrategy(); virtual ~IFilterStrategy(); + struct Progress + { + QString status; /// Status message (example: "Building foo.cpp") + int percent; /// Percentage from 0-100; -1 indicates no progress could be parsed + }; + /** * Examine if a given line contains output that is defined as an error (E.g. from a script or from a compiler, or other). * @param line the line to examine @@ -56,10 +63,24 @@ **/ virtual FilteredItem actionInLine(QString const& line) = 0; -}; - + /** + * Examine if a given line contains output which reports progress information + * + * E.g. `make` reports progress like this: + * @code + * [ 5%] Doing something + * [ 6%] Doing something + * @encode + * + * @return Processed percent & status of the output, default implementation returns default-constructed value + */ + virtual Progress progressInLine(const QString& line); +}; } // namespace KDevelop +Q_DECLARE_METATYPE(KDevelop::IFilterStrategy*) +Q_DECLARE_METATYPE(KDevelop::IFilterStrategy::Progress); + #endif // KDEVPLATFORM_IFILTERSTRATEGY_H diff --git a/outputview/ifilterstrategy.cpp b/outputview/ifilterstrategy.cpp --- a/outputview/ifilterstrategy.cpp +++ b/outputview/ifilterstrategy.cpp @@ -1,6 +1,7 @@ /* Interface description for KDevelop OutputView Filter strategies Copyright (C) 2012 Morten Danielsen Volden mvolden2@gmail.com + Copyright (C) 2016 Kevin Funk This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,4 +32,10 @@ { } +IFilterStrategy::Progress IFilterStrategy::progressInLine(const QString& line) +{ + Q_UNUSED(line); + return {QString() , -1}; +} + } diff --git a/outputview/outputexecutejob.h b/outputview/outputexecutejob.h --- a/outputview/outputexecutejob.h +++ b/outputview/outputexecutejob.h @@ -155,11 +155,16 @@ void setJobName( const QString& name ); /** - * Set the filtering strategy for the output model. + * Set one of the standard filtering strategies for the output model. */ void setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ); /** + * Set the filtering strategy for the output model. + */ + void setFilteringStrategy(IFilterStrategy* filterStrategy); + + /** * Get the current properties of the job. * * @note Default-set properties are: \ref DisplayStdout. @@ -237,6 +242,7 @@ virtual void childProcessError( QProcess::ProcessError processError ); private: + friend class OutputExecuteJobPrivate; OutputExecuteJobPrivate* d; Q_PRIVATE_SLOT(d, void childProcessStdout()); diff --git a/outputview/outputexecutejob.cpp b/outputview/outputexecutejob.cpp --- a/outputview/outputexecutejob.cpp +++ b/outputview/outputexecutejob.cpp @@ -40,6 +40,8 @@ void childProcessStdout(); void childProcessStderr(); + void emitProgress(const IFilterStrategy::Progress& progress); + QString joinCommandLine() const; QString getJobName(); @@ -55,6 +57,7 @@ OutputExecuteJob::JobStatus m_status; OutputExecuteJob::JobProperties m_properties; OutputModel::OutputFilterStrategy m_filteringStrategy; + QScopedPointer m_filteringStrategyPtr; QStringList m_arguments; QStringList m_privilegedExecutionCommand; QUrl m_workingDirectory; @@ -221,9 +224,18 @@ } Q_ASSERT( model() ); - model()->setFilteringStrategy( d->m_filteringStrategy ); + if (d->m_filteringStrategy != OutputModel::NoFilter) { + model()->setFilteringStrategy(d->m_filteringStrategy); + } else { + model()->setFilteringStrategy(d->m_filteringStrategyPtr.take()); + } + setDelegate( new OutputDelegate ); + connect(model(), &OutputModel::progress, this, [&](const IFilterStrategy::Progress& progress) { + d->emitProgress(progress); + }); + // Slots hasRawStdout() and hasRawStderr() are responsible // for feeding raw data to the line maker; so property-based channel filtering is implemented there. if( d->m_properties.testFlag( PostProcessOutput ) ) { @@ -368,6 +380,17 @@ } } +void OutputExecuteJobPrivate::emitProgress(const IFilterStrategy::Progress& progress) +{ + m_owner->emitPercent(progress.percent, 100); + + if (progress.percent == 100) { + m_owner->infoMessage(m_owner, i18n("Build finished")); + } else { + m_owner->infoMessage(m_owner, progress.status); + } +} + void OutputExecuteJob::postProcessStdout( const QStringList& lines ) { model()->appendLines( lines ); @@ -381,6 +404,17 @@ void OutputExecuteJob::setFilteringStrategy( OutputModel::OutputFilterStrategy strategy ) { d->m_filteringStrategy = strategy; + + // clear the other + d->m_filteringStrategyPtr.reset(nullptr); +} + +void OutputExecuteJob::setFilteringStrategy(IFilterStrategy* filterStrategy) +{ + d->m_filteringStrategyPtr.reset(filterStrategy); + + // clear the other + d->m_filteringStrategy = OutputModel::NoFilter; } OutputExecuteJob::JobProperties OutputExecuteJob::properties() const diff --git a/outputview/outputfilteringstrategies.h b/outputview/outputfilteringstrategies.h --- a/outputview/outputfilteringstrategies.h +++ b/outputview/outputfilteringstrategies.h @@ -26,27 +26,23 @@ #include "ifilterstrategy.h" -#include "outputformats.h" - -#include - #include -#define KDEVPLATFORMOUTPUTVIEW_TEST_EXPORT KDEVPLATFORMOUTPUTVIEW_EXPORT - #include #include -#include +#include #include namespace KDevelop { +struct CompilerFilterStrategyPrivate; + /** * This filter strategy is for not applying any filtering at all. Implementation of the * interface methods are basically noops **/ -class KDEVPLATFORMOUTPUTVIEW_TEST_EXPORT NoFilterStrategy final : public IFilterStrategy +class KDEVPLATFORMOUTPUTVIEW_EXPORT NoFilterStrategy : public IFilterStrategy { public: @@ -62,34 +58,27 @@ * This filter stategy checks if a given line contains output * that is defined as an error (or an action) from a compiler. **/ -class KDEVPLATFORMOUTPUTVIEW_TEST_EXPORT CompilerFilterStrategy final : public IFilterStrategy +class KDEVPLATFORMOUTPUTVIEW_EXPORT CompilerFilterStrategy : public IFilterStrategy { public: CompilerFilterStrategy(const QUrl& buildDir); + virtual ~CompilerFilterStrategy(); virtual FilteredItem errorInLine(const QString& line) override; virtual FilteredItem actionInLine(const QString& line) override; QVector getCurrentDirs(); private: - KDevelop::Path pathForFile( const QString& ) const; - bool isMultiLineCase(ErrorFormat curErrFilter) const; - void putDirAtEnd(const KDevelop::Path& pathToInsert); - - QVector m_currentDirs; - KDevelop::Path m_buildDir; - - using PositionMap = QHash; - PositionMap m_positionInCurrentDirs; + CompilerFilterStrategyPrivate* const d; }; /** * This filter stategy filters out errors (no actions) from Python and PHP scripts. **/ -class KDEVPLATFORMOUTPUTVIEW_TEST_EXPORT ScriptErrorFilterStrategy final : public IFilterStrategy +class KDEVPLATFORMOUTPUTVIEW_EXPORT ScriptErrorFilterStrategy : public IFilterStrategy { public: @@ -107,7 +96,7 @@ * This is especially useful for runtime output of Qt applications, for example lines such as: * "ASSERT: "errors().isEmpty()" in file /tmp/foo/bar.cpp", line 49" */ -class KDEVPLATFORMOUTPUTVIEW_TEST_EXPORT NativeAppErrorFilterStrategy final : public IFilterStrategy +class KDEVPLATFORMOUTPUTVIEW_EXPORT NativeAppErrorFilterStrategy : public IFilterStrategy { public: NativeAppErrorFilterStrategy(); @@ -119,7 +108,7 @@ /** * This filter stategy filters out errors (no actions) from Static code analysis tools (Cppcheck,) **/ -class KDEVPLATFORMOUTPUTVIEW_TEST_EXPORT StaticAnalysisFilterStrategy final : public IFilterStrategy +class KDEVPLATFORMOUTPUTVIEW_EXPORT StaticAnalysisFilterStrategy : public IFilterStrategy { public: diff --git a/outputview/outputfilteringstrategies.cpp b/outputview/outputfilteringstrategies.cpp --- a/outputview/outputfilteringstrategies.cpp +++ b/outputview/outputfilteringstrategies.cpp @@ -27,11 +27,10 @@ #include -using namespace KDevelop; +namespace KDevelop +{ -namespace { -template -FilteredItem match(const ErrorFormats& errorFormats, const QString& line) +FilteredItem match(const QVector& errorFormats, const QString& line) { FilteredItem item(line); for( const ErrorFormat& curErrFilter : errorFormats ) { @@ -58,7 +57,6 @@ } return item; } -} /// --- No filter strategy --- @@ -78,12 +76,28 @@ /// --- Compiler error filter strategy --- -CompilerFilterStrategy::CompilerFilterStrategy(const QUrl& buildDir) +/// Impl. of CompilerFilterStrategy. +struct CompilerFilterStrategyPrivate +{ + CompilerFilterStrategyPrivate(const QUrl& buildDir); + Path pathForFile( const QString& ) const; + bool isMultiLineCase(ErrorFormat curErrFilter) const; + void putDirAtEnd(const Path& pathToInsert); + + QVector m_currentDirs; + Path m_buildDir; + + using PositionMap = QHash; + PositionMap m_positionInCurrentDirs; +}; + + +CompilerFilterStrategyPrivate::CompilerFilterStrategyPrivate(const QUrl& buildDir) : m_buildDir(buildDir) { } -Path CompilerFilterStrategy::pathForFile(const QString& filename) const +Path CompilerFilterStrategyPrivate::pathForFile(const QString& filename) const { QFileInfo fi( filename ); Path currentPath; @@ -104,17 +118,17 @@ return currentPath; } -bool CompilerFilterStrategy::isMultiLineCase(KDevelop::ErrorFormat curErrFilter) const +bool CompilerFilterStrategyPrivate::isMultiLineCase(KDevelop::ErrorFormat curErrFilter) const { if(curErrFilter.compiler == QLatin1String("gfortran") || curErrFilter.compiler == QLatin1String("cmake")) { return true; } return false; } -void CompilerFilterStrategy::putDirAtEnd(const Path& pathToInsert) +void CompilerFilterStrategyPrivate::putDirAtEnd(const Path& pathToInsert) { - auto it = m_positionInCurrentDirs.find( pathToInsert ); + CompilerFilterStrategyPrivate::PositionMap::iterator it = m_positionInCurrentDirs.find( pathToInsert ); // Encountered new build directory? if (it == m_positionInCurrentDirs.end()) { m_currentDirs.push_back( pathToInsert ); @@ -127,11 +141,21 @@ } } +CompilerFilterStrategy::CompilerFilterStrategy(const QUrl& buildDir) +: d(new CompilerFilterStrategyPrivate( buildDir )) +{ +} + +CompilerFilterStrategy::~CompilerFilterStrategy() +{ + delete d; +} + QVector CompilerFilterStrategy::getCurrentDirs() { QVector ret; - ret.reserve(m_currentDirs.size()); - for (const auto& path : m_currentDirs) { + ret.reserve(d->m_currentDirs.size()); + for (const auto& path : d->m_currentDirs) { ret << path.pathOrUrl(); } return ret; @@ -187,8 +211,8 @@ if( curActFilter.tool == "cd" ) { const Path path(match.captured(curActFilter.fileGroup)); - m_currentDirs.push_back( path ); - m_positionInCurrentDirs.insert( path , m_currentDirs.size() - 1 ); + d->m_currentDirs.push_back( path ); + d->m_positionInCurrentDirs.insert( path , d->m_currentDirs.size() - 1 ); } // Special case for cmake: we parse the "Compiling " expression @@ -199,7 +223,7 @@ if ( curActFilter.fileGroup != -1 && curActFilter.tool == "cmake" && line.contains("Building")) { const auto objectFile = match.captured(curActFilter.fileGroup); const auto dir = objectFile.section(QStringLiteral("CMakeFiles/"), 0, 0); - putDirAtEnd(Path(m_buildDir, dir)); + d->putDirAtEnd(Path(d->m_buildDir, dir)); } break; } @@ -229,7 +253,7 @@ }; // A list of filters for possible compiler, linker, and make errors - static const ErrorFormat ERROR_FILTERS[] = { + static const QVector ERROR_FILTERS = { #ifdef Q_OS_WIN // MSVC ErrorFormat( QStringLiteral("^([a-zA-Z]:\\\\.+)\\(([1-9][0-9]*)\\): ((?:error|warning) .+\\:).*$"), 1, 2, 3 ), @@ -282,11 +306,11 @@ { if(curErrFilter.fileGroup > 0) { if( curErrFilter.compiler == "cmake" ) { // Unfortunately we cannot know if an error or an action comes first in cmake, and therefore we need to do this - if( m_currentDirs.empty() ) { - putDirAtEnd( m_buildDir.parent() ); + if( d->m_currentDirs.empty() ) { + d->putDirAtEnd( d->m_buildDir.parent() ); } } - item.url = pathForFile( match.captured( curErrFilter.fileGroup ) ).toUrl(); + item.url = d->pathForFile( match.captured( curErrFilter.fileGroup ) ).toUrl(); } item.lineNo = match.captured( curErrFilter.lineGroup ).toInt() - 1; if(curErrFilter.columnGroup >= 0) { @@ -313,7 +337,7 @@ if(item.type == FilteredItem::InvalidItem) { // If there are no error indicators in the line // maybe this is a multiline case - if(isMultiLineCase(curErrFilter)) { + if(d->isMultiLineCase(curErrFilter)) { item.type = FilteredItem::ErrorItem; } else { // Okay so we couldn't find anything to indicate an error, but we have file and lineGroup @@ -343,7 +367,7 @@ FilteredItem ScriptErrorFilterStrategy::errorInLine(const QString& line) { // A list of filters for possible Python and PHP errors - static const ErrorFormat SCRIPT_ERROR_FILTERS[] = { + static const QVector SCRIPT_ERROR_FILTERS = { ErrorFormat( QStringLiteral("^ File \"(.*)\", line ([0-9]+)(.*$|, in(.*)$)"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.*(/.*):([0-9]+).*$"), 1, 2, -1 ), ErrorFormat( QStringLiteral("^.* in (/.*) on line ([0-9]+).*$"), 1, 2, -1 ) @@ -365,7 +389,7 @@ FilteredItem NativeAppErrorFilterStrategy::errorInLine(const QString& line) { - static const ErrorFormat NATIVE_APPLICATION_ERROR_FILTERS[] = { + static const QVector NATIVE_APPLICATION_ERROR_FILTERS = { // BEGIN: C++ // a.out: test.cpp:5: int main(): Assertion `false' failed. @@ -417,7 +441,7 @@ FilteredItem StaticAnalysisFilterStrategy::errorInLine(const QString& line) { // A list of filters for static analysis tools (krazy2, cppcheck) - static const ErrorFormat STATIC_ANALYSIS_FILTERS[] = { + static const QVector STATIC_ANALYSIS_FILTERS = { // CppCheck ErrorFormat( QStringLiteral("^\\[(.*):([0-9]+)\\]:(.*)"), 1, 2, 3 ), // krazy2 @@ -428,3 +452,5 @@ return match(STATIC_ANALYSIS_FILTERS, line); } + +} diff --git a/outputview/outputmodel.h b/outputview/outputmodel.h --- a/outputview/outputmodel.h +++ b/outputview/outputmodel.h @@ -24,6 +24,7 @@ #include "outputviewexport.h" #include "ioutputviewmodel.h" +#include "ifilterstrategy.h" #include #include @@ -68,13 +69,16 @@ QVariant headerData( int, Qt::Orientation, int = Qt::DisplayRole ) const override; void setFilteringStrategy(const OutputFilterStrategy& currentStrategy); + void setFilteringStrategy(IFilterStrategy* filterStrategy); public Q_SLOTS: void appendLine( const QString& ); void appendLines( const QStringList& ); void ensureAllDone(); signals: + /// If the current filter strategy supports it, reports progress information + void progress(const KDevelop::IFilterStrategy::Progress& progress); void allDone(); private: diff --git a/outputview/outputmodel.cpp b/outputview/outputmodel.cpp --- a/outputview/outputmodel.cpp +++ b/outputview/outputmodel.cpp @@ -40,7 +40,6 @@ #include Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(KDevelop::IFilterStrategy*) namespace KDevelop { @@ -101,6 +100,7 @@ signals: void parsedBatch(const QVector& filteredItems); + void progress(const KDevelop::IFilterStrategy::Progress& progress); void allDone(); private slots: @@ -125,6 +125,12 @@ filteredItems << item; + auto progress = m_filter->progressInLine(line); + if (progress.percent >= 0 && m_progress.percent != progress.percent) { + m_progress = progress; + emit this->progress(m_progress); + } + if( filteredItems.size() == BATCH_SIZE ) { emit parsedBatch(filteredItems); filteredItems.clear(); @@ -144,6 +150,7 @@ QStringList m_cachedLines; QTimer* m_timer; + IFilterStrategy::Progress m_progress; }; class ParsingThread @@ -209,11 +216,15 @@ { qRegisterMetaType >(); qRegisterMetaType(); + qRegisterMetaType(); + s_parsingThread->addWorker(worker); model->connect(worker, &ParseWorker::parsedBatch, model, [=] (const QVector& items) { linesParsed(items); }); model->connect(worker, &ParseWorker::allDone, model, &OutputModel::allDone); + model->connect(worker, &ParseWorker::progress, + model, &OutputModel::progress); } bool OutputModelPrivate::isValidIndex( const QModelIndex& idx, int currentRowCount ) const @@ -396,6 +407,12 @@ Q_ARG(KDevelop::IFilterStrategy*, filter)); } +void OutputModel::setFilteringStrategy(IFilterStrategy* filterStrategy) +{ + QMetaObject::invokeMethod(d->worker, "changeFilterStrategy", + Q_ARG(KDevelop::IFilterStrategy*, filterStrategy)); +} + void OutputModel::appendLines( const QStringList& lines ) { if( lines.isEmpty() )