diff --git a/src/libs/utils/outputformatter.cpp b/src/libs/utils/outputformatter.cpp index eb7f1cebb6e..7d8ac409edf 100644 --- a/src/libs/utils/outputformatter.cpp +++ b/src/libs/utils/outputformatter.cpp @@ -374,6 +374,7 @@ const QList OutputFormatter::linkifiedText( int nextLinkSpecIndex = 0; for (const FormattedText &t : text) { + const int totalPreviousTextLength = totalTextLengthSoFar; // There is no more linkification work to be done. Just copy the text as-is. if (nextLinkSpecIndex >= linkSpecs.size()) { @@ -391,7 +392,7 @@ const QList OutputFormatter::linkifiedText( } const OutputLineParser::LinkSpec &linkSpec = linkSpecs.at(nextLinkSpecIndex); - const int localLinkStartPos = linkSpec.startPos - totalTextLengthSoFar; + const int localLinkStartPos = linkSpec.startPos - totalPreviousTextLength; ++nextLinkSpecIndex; // We ignore links that would cross format boundaries. diff --git a/src/libs/utils/outputformatter.h b/src/libs/utils/outputformatter.h index dcc5b5f76cb..d91f96fa131 100644 --- a/src/libs/utils/outputformatter.h +++ b/src/libs/utils/outputformatter.h @@ -159,6 +159,9 @@ public: bool hasFatalErrors() const; + static const QList linkifiedText(const QList &text, + const OutputLineParser::LinkSpecs &linkSpecs); + #ifdef WITH_TESTS void overrideTextCharFormat(const QTextCharFormat &fmt); QList lineParsers() const; @@ -185,8 +188,6 @@ private: void dumpIncompleteLine(const QString &line, OutputFormat format); void clearLastLine(); QList parseAnsi(const QString &text, const QTextCharFormat &format); - const QList linkifiedText(const QList &text, - const OutputLineParser::LinkSpecs &linkSpecs); OutputFormat outputTypeForParser(const OutputLineParser *parser, OutputFormat type) const; void setupLineParser(OutputLineParser *parser); diff --git a/src/plugins/baremetal/iarewparser.cpp b/src/plugins/baremetal/iarewparser.cpp index 4ee139c7781..01ce9de32ed 100644 --- a/src/plugins/baremetal/iarewparser.cpp +++ b/src/plugins/baremetal/iarewparser.cpp @@ -231,7 +231,7 @@ void IarParser::flush() m_lastTask.details = m_snippets; m_snippets.clear(); m_lines += m_lastTask.details.count(); - setMonospacedDetailsFormat(m_lastTask); + setDetailsFormat(m_lastTask); amendFilePath(); m_expectSnippet = true; diff --git a/src/plugins/baremetal/keilparser.cpp b/src/plugins/baremetal/keilparser.cpp index cc0eb1b1b8d..5ee130073eb 100644 --- a/src/plugins/baremetal/keilparser.cpp +++ b/src/plugins/baremetal/keilparser.cpp @@ -258,7 +258,7 @@ void KeilParser::flush() m_lastTask.details = m_snippets; m_snippets.clear(); m_lines += m_lastTask.details.count(); - setMonospacedDetailsFormat(m_lastTask); + setDetailsFormat(m_lastTask); Task t = m_lastTask; m_lastTask.clear(); scheduleTask(t, m_lines, 1); diff --git a/src/plugins/baremetal/sdccparser.cpp b/src/plugins/baremetal/sdccparser.cpp index b7e60aafebc..5a6b23c0d0c 100644 --- a/src/plugins/baremetal/sdccparser.cpp +++ b/src/plugins/baremetal/sdccparser.cpp @@ -152,7 +152,7 @@ void SdccParser::flush() if (m_lastTask.isNull()) return; - setMonospacedDetailsFormat(m_lastTask); + setDetailsFormat(m_lastTask); Task t = m_lastTask; m_lastTask.clear(); scheduleTask(t, m_lines, 1); diff --git a/src/plugins/projectexplorer/clangparser.cpp b/src/plugins/projectexplorer/clangparser.cpp index 9198ea5af37..4c34209671e 100644 --- a/src/plugins/projectexplorer/clangparser.cpp +++ b/src/plugins/projectexplorer/clangparser.cpp @@ -75,7 +75,7 @@ OutputLineParser::Result ClangParser::handleLine(const QString &line, OutputForm match = m_commandRegExp.match(lne); if (match.hasMatch()) { m_expectSnippet = true; - newTask(CompileTask(taskType(match.captured(3)), match.captured(4))); + createOrAmendTask(taskType(match.captured(3)), match.captured(4), lne); return Status::InProgress; } @@ -86,7 +86,7 @@ OutputLineParser::Result ClangParser::handleLine(const QString &line, OutputForm const int lineNo = match.captured(3).toInt(); LinkSpecs linkSpecs; addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 2); - newTask(CompileTask(Task::Unknown, lne.trimmed(), filePath, lineNo)); + createOrAmendTask(Task::Unknown, lne.trimmed(), lne, false, filePath, lineNo, linkSpecs); return {Status::InProgress, linkSpecs}; } @@ -100,19 +100,20 @@ OutputLineParser::Result ClangParser::handleLine(const QString &line, OutputForm const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1))); LinkSpecs linkSpecs; addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1); - newTask(CompileTask(taskType(match.captured(7)), match.captured(8), filePath, lineNo)); + createOrAmendTask(taskType(match.captured(7)), match.captured(8), lne, false, + filePath, lineNo, linkSpecs); return {Status::InProgress, linkSpecs}; } match = m_codesignRegExp.match(lne); if (match.hasMatch()) { m_expectSnippet = true; - newTask(CompileTask(Task::Error, match.captured(1))); + createOrAmendTask(Task::Error, match.captured(1), lne, false); return Status::InProgress; } if (m_expectSnippet) { - amendDescription(lne); + createOrAmendTask(Task::Unknown, lne, lne, true); return Status::InProgress; } @@ -178,17 +179,15 @@ void ProjectExplorerPlugin::testClangOutputParser_data() " ^") << OutputParserTester::STDERR << QString() << QString() - << (Tasks() - << CompileTask(Task::Unknown, - "In file included from ..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h:45:", - FilePath::fromUserInput("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h"), - 45) - << CompileTask(Task::Warning, - "unknown attribute 'dllimport' ignored [-Wunknown-attributes]\n" - "class Q_CORE_EXPORT QSysInfo {\n" - " ^", - FilePath::fromUserInput("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h"), - 1425)) + << Tasks{CompileTask( + Task::Warning, + "unknown attribute 'dllimport' ignored [-Wunknown-attributes]\n" + "In file included from ..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h:45:\n" + "..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h(1425) : warning: unknown attribute 'dllimport' ignored [-Wunknown-attributes]\n" + "class Q_CORE_EXPORT QSysInfo {\n" + " ^", + FilePath::fromUserInput("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h"), + 1425)} << QString(); QTest::newRow("note") @@ -200,6 +199,7 @@ void ProjectExplorerPlugin::testClangOutputParser_data() << (Tasks() << CompileTask(Task::Unknown, "instantiated from:\n" + "..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h:1289:27: note: instantiated from:\n" "# define Q_CORE_EXPORT Q_DECL_IMPORT\n" " ^", FilePath::fromUserInput("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h"), @@ -215,6 +215,7 @@ void ProjectExplorerPlugin::testClangOutputParser_data() << (Tasks() << CompileTask(Task::Error, "'bits/c++config.h' file not found\n" + "/usr/include/c++/4.6/utility:68:10: fatal error: 'bits/c++config.h' file not found\n" "#include \n" " ^", FilePath::fromUserInput("/usr/include/c++/4.6/utility"), @@ -230,6 +231,7 @@ void ProjectExplorerPlugin::testClangOutputParser_data() << (Tasks() << CompileTask(Task::Warning, "?: has lower precedence than +; + will be evaluated first [-Wparentheses]\n" + "/home/code/src/creator/src/plugins/coreplugin/manhattanstyle.cpp:567:51: warning: ?: has lower precedence than +; + will be evaluated first [-Wparentheses]\n" " int x = option->rect.x() + horizontal ? 2 : 6;\n" " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^", FilePath::fromUserInput("/home/code/src/creator/src/plugins/coreplugin/manhattanstyle.cpp"), diff --git a/src/plugins/projectexplorer/gccparser.cpp b/src/plugins/projectexplorer/gccparser.cpp index 2f2653703e4..9a55283b97c 100644 --- a/src/plugins/projectexplorer/gccparser.cpp +++ b/src/plugins/projectexplorer/gccparser.cpp @@ -32,6 +32,8 @@ #include +#include + using namespace ProjectExplorer; using namespace Utils; @@ -69,11 +71,45 @@ QList GccParser::gccParserSuite() return {new GccParser, new Internal::LldParser, new LdParser}; } -void GccParser::newTask(const Task &task) +void GccParser::createOrAmendTask( + Task::TaskType type, + const QString &description, + const QString &originalLine, + bool forceAmend, + const FilePath &file, + int line, + const LinkSpecs &linkSpecs + ) { - flush(); - m_currentTask = task; - m_lines = 1; + const bool amend = !m_currentTask.isNull() && (forceAmend || isContinuation(originalLine)); + if (!amend) { + flush(); + m_currentTask = CompileTask(type, description, file, line); + m_currentTask.details.append(originalLine); + m_linkSpecs = linkSpecs; + m_lines = 1; + return; + } + + LinkSpecs adaptedLinkSpecs = linkSpecs; + const int offset = std::accumulate(m_currentTask.details.cbegin(), m_currentTask.details.cend(), + 0, [](int total, const QString &line) { return total + line.length() + 1;}); + for (LinkSpec &ls : adaptedLinkSpecs) + ls.startPos += offset; + m_linkSpecs << adaptedLinkSpecs; + m_currentTask.details.append(originalLine); + + // Check whether the new line is more relevant than the previous ones. + if ((m_currentTask.type != Task::Error && type == Task::Error) + || (m_currentTask.type == Task::Unknown && type != Task::Unknown)) { + m_currentTask.type = type; + m_currentTask.summary = description; + if (!file.isEmpty()) { + m_currentTask.setFile(file); + m_currentTask.line = line; + } + } + ++m_lines; } void GccParser::flush() @@ -81,22 +117,19 @@ void GccParser::flush() if (m_currentTask.isNull()) return; - setMonospacedDetailsFormat(m_currentTask); + // If there is only one line of details, then it is the line that we generated + // the summary from. Remove it, because it does not add any information. + if (m_currentTask.details.count() == 1) + m_currentTask.details.clear(); + + setDetailsFormat(m_currentTask, m_linkSpecs); Task t = m_currentTask; m_currentTask.clear(); + m_linkSpecs.clear(); scheduleTask(t, m_lines, 1); m_lines = 0; } -void GccParser::amendDescription(const QString &desc) -{ - if (m_currentTask.isNull()) - return; - m_currentTask.details.append(desc); - ++m_lines; - return; -} - OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat type) { if (type == StdOutFormat) { @@ -114,7 +147,7 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat // Handle misc issues: if (lne.startsWith(QLatin1String("ERROR:")) || lne == QLatin1String("* cpp failed")) { - newTask(CompileTask(Task::Error, lne /* description */)); + createOrAmendTask(Task::Error, lne, lne); return Status::InProgress; } @@ -128,10 +161,20 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat } else if (description.startsWith(QLatin1String("fatal: "))) { description = description.mid(7); } - newTask(CompileTask(type, description)); + createOrAmendTask(type, description, lne); return Status::InProgress; } + match = m_regExpIncluded.match(lne); + if (match.hasMatch()) { + const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1))); + const int lineNo = match.captured(3).toInt(); + LinkSpecs linkSpecs; + addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1); + createOrAmendTask(Task::Unknown, lne.trimmed(), lne, false, filePath, lineNo, linkSpecs); + return {Status::InProgress, linkSpecs}; + } + match = m_regExp.match(lne); if (match.hasMatch()) { int lineno = match.captured(3).toInt(); @@ -151,20 +194,12 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1))); LinkSpecs linkSpecs; addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineno, match, 1); - newTask(CompileTask(type, description, filePath, lineno)); + createOrAmendTask(type, description, lne, false, filePath, lineno, linkSpecs); return {Status::InProgress, linkSpecs}; } - match = m_regExpIncluded.match(lne); - if (match.hasMatch()) { - const FilePath filePath = absoluteFilePath(FilePath::fromUserInput(match.captured(1))); - const int lineNo = match.captured(3).toInt(); - LinkSpecs linkSpecs; - addLinkSpecForAbsoluteFilePath(linkSpecs, filePath, lineNo, match, 1); - newTask(CompileTask(Task::Unknown, lne.trimmed() /* description */, filePath, lineNo)); - return {Status::InProgress, linkSpecs}; - } else if (lne.startsWith(' ') && !m_currentTask.isNull()) { - amendDescription(lne); + if ((lne.startsWith(' ') && !m_currentTask.isNull()) || isContinuation(lne)) { + createOrAmendTask(Task::Unknown, lne, lne, true); return Status::InProgress; } @@ -172,6 +207,15 @@ OutputLineParser::Result GccParser::handleLine(const QString &line, OutputFormat return Status::NotHandled; } +bool GccParser::isContinuation(const QString &newLine) const +{ + return !m_currentTask.isNull() + && (m_currentTask.details.last().endsWith(':') + || m_currentTask.details.last().endsWith(',') + || newLine.contains("within this context") + || newLine.contains("note:")); +} + // Unit tests: #ifdef WITH_TESTS @@ -590,22 +634,14 @@ void ProjectExplorerPlugin::testGccOutputParsers_data() "C:/Symbian_SDK/epoc32/include/e32cmn.inl:7094: warning: returning reference to temporary") << OutputParserTester::STDERR << QString() << QString() - << (Tasks() - << CompileTask(Task::Unknown, - "In file included from C:/Symbian_SDK/epoc32/include/e32cmn.h:6792,", - FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32cmn.h"), - 6792) - << CompileTask(Task::Unknown, - "from C:/Symbian_SDK/epoc32/include/e32std.h:25,", - FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32std.h"), - 25) - << CompileTask(Task::Unknown, - "In member function 'SSecureId::operator const TSecureId&() const':", - FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32cmn.inl")) - << CompileTask(Task::Warning, - "returning reference to temporary", - FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32cmn.inl"), - 7094)) + << Tasks{CompileTask(Task::Warning, + "returning reference to temporary\n" + "In file included from C:/Symbian_SDK/epoc32/include/e32cmn.h:6792,\n" + " from C:/Symbian_SDK/epoc32/include/e32std.h:25,\n" + "C:/Symbian_SDK/epoc32/include/e32cmn.inl: In member function 'SSecureId::operator const TSecureId&() const':\n" + "C:/Symbian_SDK/epoc32/include/e32cmn.inl:7094: warning: returning reference to temporary", + FilePath::fromUserInput("C:/Symbian_SDK/epoc32/include/e32cmn.inl"), + 7094)} << QString(); QTest::newRow("QTCREATORBUG-2206") @@ -624,19 +660,14 @@ void ProjectExplorerPlugin::testGccOutputParsers_data() "/Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh:1134:26: warning: no newline at end of file") << OutputParserTester::STDERR << QString() << QString() - << (Tasks() - << CompileTask(Task::Unknown, - "In file included from /Symbian/SDK/EPOC32/INCLUDE/GCCE/GCCE.h:15,", - FilePath::fromUserInput("/Symbian/SDK/EPOC32/INCLUDE/GCCE/GCCE.h"), - 15) - << CompileTask(Task::Unknown, - "from :26:", - FilePath::fromUserInput(""), - 26) - << CompileTask(Task::Warning, - "no newline at end of file", - FilePath::fromUserInput("/Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh"), - 1134)) + << Tasks{CompileTask( + Task::Warning, + "no newline at end of file\n" + "In file included from /Symbian/SDK/EPOC32/INCLUDE/GCCE/GCCE.h:15,\n" + " from :26:\n" + "/Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh:1134:26: warning: no newline at end of file", + FilePath::fromUserInput("/Symbian/SDK/epoc32/include/variant/Symbian_OS.hrh"), + 1134)} << QString(); QTest::newRow("Linker fail (release build)") @@ -669,14 +700,12 @@ void ProjectExplorerPlugin::testGccOutputParsers_data() "./mw.h:4:0: warning: \"STUPID_DEFINE\" redefined") << OutputParserTester::STDERR << QString() << QString() - << (Tasks() - << CompileTask(Task::Unknown, - "In file included from :0:0:", - FilePath::fromUserInput("")) - << CompileTask(Task::Warning, - "\"STUPID_DEFINE\" redefined", - FilePath::fromUserInput("./mw.h"), - 4)) + << Tasks{CompileTask( + Task::Warning, + "\"STUPID_DEFINE\" redefined\n" + "In file included from :0:0:\n" + "./mw.h:4:0: warning: \"STUPID_DEFINE\" redefined", + FilePath::fromUserInput("./mw.h"), 4)} << QString(); QTest::newRow("instanciation with line:column info") @@ -736,17 +765,14 @@ void ProjectExplorerPlugin::testGccOutputParsers_data() " ^") << OutputParserTester::STDERR << QString() << QString() - << (Tasks() - << CompileTask(Task::Unknown, - "In file included from /home/code/src/creator/src/libs/extensionsystem/pluginerrorview.cpp:31:0:", - FilePath::fromUserInput("/home/code/src/creator/src/libs/extensionsystem/pluginerrorview.cpp"), - 31) - << CompileTask(Task::Error, - "QtGui/QAction: No such file or directory\n" - " #include \n" - " ^", - FilePath::fromUserInput(".uic/ui_pluginerrorview.h"), - 14)) + << Tasks{CompileTask( + Task::Error, + "QtGui/QAction: No such file or directory\n" + "In file included from /home/code/src/creator/src/libs/extensionsystem/pluginerrorview.cpp:31:0:\n" + ".uic/ui_pluginerrorview.h:14:25: fatal error: QtGui/QAction: No such file or directory\n" + " #include \n" + " ^", + FilePath::fromUserInput(".uic/ui_pluginerrorview.h"), 14)} << QString(); QTest::newRow("qtcreatorbug-9195") @@ -757,26 +783,15 @@ void ProjectExplorerPlugin::testGccOutputParsers_data() "main.cpp:7:22: error: within this context") << OutputParserTester::STDERR << QString() << QString() - << (Tasks() - << CompileTask(Task::Unknown, - "In file included from /usr/include/qt4/QtCore/QString:1:0,", - FilePath::fromUserInput("/usr/include/qt4/QtCore/QString"), - 1) - << CompileTask(Task::Unknown, - "from main.cpp:3:", - FilePath::fromUserInput("main.cpp"), - 3) - << CompileTask(Task::Unknown, - "In function 'void foo()':", - FilePath::fromUserInput("/usr/include/qt4/QtCore/qstring.h")) - << CompileTask(Task::Error, - "'QString::QString(const char*)' is private", - FilePath::fromUserInput("/usr/include/qt4/QtCore/qstring.h"), - 597) - << CompileTask(Task::Error, - "within this context", - FilePath::fromUserInput("main.cpp"), - 7)) + << Tasks{CompileTask( + Task::Error, + "'QString::QString(const char*)' is private\n" + "In file included from /usr/include/qt4/QtCore/QString:1:0,\n" + " from main.cpp:3:\n" + "/usr/include/qt4/QtCore/qstring.h: In function 'void foo()':\n" + "/usr/include/qt4/QtCore/qstring.h:597:5: error: 'QString::QString(const char*)' is private\n" + "main.cpp:7:22: error: within this context", + FilePath::fromUserInput("/usr/include/qt4/QtCore/qstring.h"), 597)} << QString(); QTest::newRow("ld: Multiple definition error") @@ -1041,71 +1056,55 @@ void ProjectExplorerPlugin::testGccOutputParsers_data() ) << OutputParserTester::STDERR << QString() << QString() - << Tasks({CompileTask(Task::Unknown, - "In file included from /usr/include/qt/QtCore/qlocale.h:43,", - FilePath::fromUserInput("/usr/include/qt/QtCore/qlocale.h"), 43), - CompileTask(Task::Unknown, - "from /usr/include/qt/QtCore/qtextstream.h:46,", - FilePath::fromUserInput("/usr/include/qt/QtCore/qtextstream.h"), 46), - CompileTask(Task::Unknown, - "from /qtc/src/shared/proparser/proitems.cpp:31:", - FilePath::fromUserInput("/qtc/src/shared/proparser/proitems.cpp"), 31), - CompileTask(Task::Unknown, - "In constructor ‘QVariant::QVariant(QVariant&&)’:", - FilePath::fromUserInput("/usr/include/qt/QtCore/qvariant.h"), -1), - CompileTask(Task::Warning, - "implicitly-declared ‘constexpr QVariant::Private& QVariant::Private::operator=(const QVariant::Private&)’ is deprecated [-Wdeprecated-copy]\n" - " 273 | { other.d = Private(); }\n" - " | ^", - FilePath::fromUserInput("/usr/include/qt/QtCore/qvariant.h"), 273), - CompileTask(Task::Unknown, - "because ‘QVariant::Private’ has user-provided ‘QVariant::Private::Private(const QVariant::Private&)’\n" - " 399 | inline Private(const Private &other) Q_DECL_NOTHROW\n" - " | ^~~~~~~)", - FilePath::fromUserInput("/usr/include/qt/QtCore/qvariant.h"), 399), - CompileTask(Task::Unknown, - "In function ‘int test(const shape&, const shape&)’:", - FilePath::fromUserInput("t.cc"), -1), - CompileTask(Task::Error, - "no match for ‘operator+’ (operand types are ‘boxed_value’ and ‘boxed_value’)\n" - " 14 | return (width(s1) * height(s1)\n" - " | ~~~~~~~~~~~~~~~~~~~~~~\n" - " | |\n" - " | boxed_value<[...]>\n" - " 15 | + width(s2) * height(s2));\n" - " | ^ ~~~~~~~~~~~~~~~~~~~~~~\n" - " | |\n" - " | boxed_value<[...]>", - FilePath::fromUserInput("t.cc"), - 15), + << Tasks{CompileTask(Task::Warning, + "implicitly-declared ‘constexpr QVariant::Private& QVariant::Private::operator=(const QVariant::Private&)’ is deprecated [-Wdeprecated-copy]\n" + "In file included from /usr/include/qt/QtCore/qlocale.h:43,\n" + " from /usr/include/qt/QtCore/qtextstream.h:46,\n" + " from /qtc/src/shared/proparser/proitems.cpp:31:\n" + "/usr/include/qt/QtCore/qvariant.h: In constructor ‘QVariant::QVariant(QVariant&&)’:\n" + "/usr/include/qt/QtCore/qvariant.h:273:25: warning: implicitly-declared ‘constexpr QVariant::Private& QVariant::Private::operator=(const QVariant::Private&)’ is deprecated [-Wdeprecated-copy]\n" + " 273 | { other.d = Private(); }\n" + " | ^\n" + "/usr/include/qt/QtCore/qvariant.h:399:16: note: because ‘QVariant::Private’ has user-provided ‘QVariant::Private::Private(const QVariant::Private&)’\n" + " 399 | inline Private(const Private &other) Q_DECL_NOTHROW\n" + " | ^~~~~~~)", + FilePath::fromUserInput("/usr/include/qt/QtCore/qvariant.h"), 273), + CompileTask(Task::Unknown, "In function ‘int test(const shape&, const shape&)’:", FilePath::fromUserInput("t.cc")), // TODO: should be matched by GccParser, rather than LdParser + CompileTask(Task::Error, + "no match for ‘operator+’ (operand types are ‘boxed_value’ and ‘boxed_value’)\n" + "t.cc:15:4: error: no match for ‘operator+’ (operand types are ‘boxed_value’ and ‘boxed_value’)\n" + " 14 | return (width(s1) * height(s1)\n" + " | ~~~~~~~~~~~~~~~~~~~~~~\n" + " | |\n" + " | boxed_value<[...]>\n" + " 15 | + width(s2) * height(s2));\n" + " | ^ ~~~~~~~~~~~~~~~~~~~~~~\n" + " | |\n" + " | boxed_value<[...]>", + FilePath::fromUserInput("t.cc"), + 15), CompileTask(Task::Error, "‘string’ in namespace ‘std’ does not name a type\n" + "incomplete.c:1:6: error: ‘string’ in namespace ‘std’ does not name a type\n" " 1 | std::string test(void)\n" - " | ^~~~~~", - FilePath::fromUserInput("incomplete.c"), - 1), - CompileTask(Task::Unknown, - "‘std::string’ is defined in header ‘’; did you forget to ‘#include ’?\n" + " | ^~~~~~\n" + "incomplete.c:1:1: note: ‘std::string’ is defined in header ‘’; did you forget to ‘#include ’?\n" " +++ |+#include \n" " 1 | std::string test(void)", FilePath::fromUserInput("incomplete.c"), 1), - CompileTask(Task::Unknown, - "In function ‘caller’:", - FilePath::fromUserInput("param-type-mismatch.c"), - -1), + CompileTask(Task::Unknown, "In function ‘caller’:", FilePath::fromUserInput("param-type-mismatch.c")), // TODO: should be matched by GccParser, rather than LdParser CompileTask(Task::Warning, "passing argument 2 of ‘callee’ makes pointer from integer without a cast [-Wint-conversion]\n" + "param-type-mismatch.c:5:24: warning: passing argument 2 of ‘callee’ makes pointer from integer without a cast [-Wint-conversion]\n" " 5 | return callee(first, second, third);\n" " | ^~~~~~\n" " | |\n" - " | int", - FilePath::fromUserInput("param-type-mismatch.c"), 5), - CompileTask(Task::Unknown, - "expected ‘const char *’ but argument is of type ‘int’\n" + " | int\n" + "param-type-mismatch.c:1:40: note: expected ‘const char *’ but argument is of type ‘int’\n" " 1 | extern int callee(int one, const char *two, float three);\n" " | ~~~~~~~~~~~~^~~", - FilePath::fromUserInput("param-type-mismatch.c"), 1)}) + FilePath::fromUserInput("param-type-mismatch.c"), 5)} << QString(); } diff --git a/src/plugins/projectexplorer/gccparser.h b/src/plugins/projectexplorer/gccparser.h index 326b02a47df..346ddd8ede7 100644 --- a/src/plugins/projectexplorer/gccparser.h +++ b/src/plugins/projectexplorer/gccparser.h @@ -45,19 +45,28 @@ public: static QList gccParserSuite(); protected: - void newTask(const Task &task); + void createOrAmendTask( + Task::TaskType type, + const QString &description, + const QString &originalLine, + bool forceAmend = false, + const Utils::FilePath &file = {}, + int line = -1, + const LinkSpecs &linkSpecs = {} + ); void flush() override; - void amendDescription(const QString &desc); - private: Result handleLine(const QString &line, Utils::OutputFormat type) override; + bool isContinuation(const QString &newLine) const; + QRegularExpression m_regExp; QRegularExpression m_regExpIncluded; QRegularExpression m_regExpGccNames; Task m_currentTask; + LinkSpecs m_linkSpecs; int m_lines = 0; }; diff --git a/src/plugins/projectexplorer/ioutputparser.cpp b/src/plugins/projectexplorer/ioutputparser.cpp index 6bda7560f46..8c6e1c2b331 100644 --- a/src/plugins/projectexplorer/ioutputparser.cpp +++ b/src/plugins/projectexplorer/ioutputparser.cpp @@ -30,6 +30,7 @@ #include #include +#include /*! @@ -94,16 +95,22 @@ void OutputTaskParser::scheduleTask(const Task &task, int outputLines, int skipp QTC_CHECK(d->scheduledTasks.size() <= 2); } -void OutputTaskParser::setMonospacedDetailsFormat(Task &task) +void OutputTaskParser::setDetailsFormat(Task &task, const LinkSpecs &linkSpecs) { if (task.details.isEmpty()) return; - QTextLayout::FormatRange fr; - fr.start = task.summary.length() + 1; - fr.length = task.details.join('\n').length(); - fr.format.setFont(TextEditor::TextEditorSettings::fontSettings().font()); - fr.format.setFontStyleHint(QFont::Monospace); - task.formats = {fr}; + + Utils::FormattedText monospacedText(task.details.join('\n')); + monospacedText.format.setFont(TextEditor::TextEditorSettings::fontSettings().font()); + monospacedText.format.setFontStyleHint(QFont::Monospace); + const QList linkifiedText = + Utils::OutputFormatter::linkifiedText({monospacedText}, linkSpecs); + task.formats.clear(); + int offset = task.summary.length() + 1; + for (const Utils::FormattedText &ft : linkifiedText) { + task.formats << QTextLayout::FormatRange{offset, ft.text.length(), ft.format}; + offset += ft.text.length(); + } } void OutputTaskParser::runPostPrintActions() diff --git a/src/plugins/projectexplorer/ioutputparser.h b/src/plugins/projectexplorer/ioutputparser.h index d3d5f3c0247..b4fb8ab73c9 100644 --- a/src/plugins/projectexplorer/ioutputparser.h +++ b/src/plugins/projectexplorer/ioutputparser.h @@ -54,7 +54,7 @@ public: protected: void scheduleTask(const Task &task, int outputLines, int skippedLines = 0); - void setMonospacedDetailsFormat(Task &task); + void setDetailsFormat(Task &task, const LinkSpecs &linkSpecs = {}); private: void runPostPrintActions() override; diff --git a/src/plugins/projectexplorer/linuxiccparser.cpp b/src/plugins/projectexplorer/linuxiccparser.cpp index 99427de113f..0f2877dfda6 100644 --- a/src/plugins/projectexplorer/linuxiccparser.cpp +++ b/src/plugins/projectexplorer/linuxiccparser.cpp @@ -125,7 +125,7 @@ void LinuxIccParser::flush() if (m_temporary.isNull()) return; - setMonospacedDetailsFormat(m_temporary); + setDetailsFormat(m_temporary); Task t = m_temporary; m_temporary.clear(); scheduleTask(t, m_lines, 1); diff --git a/src/plugins/projectexplorer/msvcparser.cpp b/src/plugins/projectexplorer/msvcparser.cpp index 072e1da8f85..8cd6c447bd9 100644 --- a/src/plugins/projectexplorer/msvcparser.cpp +++ b/src/plugins/projectexplorer/msvcparser.cpp @@ -176,7 +176,7 @@ void MsvcParser::flush() if (m_lastTask.isNull()) return; - setMonospacedDetailsFormat(m_lastTask); + setDetailsFormat(m_lastTask); Task t = m_lastTask; m_lastTask.clear(); scheduleTask(t, m_lines, 1); diff --git a/src/plugins/projectexplorer/taskwindow.cpp b/src/plugins/projectexplorer/taskwindow.cpp index 9548be05521..732f29c50ea 100644 --- a/src/plugins/projectexplorer/taskwindow.cpp +++ b/src/plugins/projectexplorer/taskwindow.cpp @@ -34,13 +34,15 @@ #include #include +#include #include #include #include #include -#include #include +#include +#include #include #include @@ -77,7 +79,23 @@ class TaskView : public Utils::ListView public: TaskView(QWidget *parent = nullptr); ~TaskView() override; + +private: void resizeEvent(QResizeEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + + class Location { + public: + Utils::FilePath file; + int line; + int column; + }; + Location locationForPos(const QPoint &pos); + + bool m_linksActive = true; + Qt::MouseButton m_mouseButtonPressed = Qt::NoButton; }; class TaskWindowContext : public Core::IContext @@ -103,11 +121,14 @@ public: void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + QString hrefForPos(const QPointF &pos); + private: void generateGradientPixmap(int width, int height, QColor color, bool selected) const; mutable int m_cachedHeight = 0; mutable QFont m_cachedFont; + mutable QList> m_hrefs; /* Collapsed: @@ -186,6 +207,7 @@ TaskView::TaskView(QWidget *parent) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + setMouseTracking(true); QFontMetrics fm(font()); int vStepSize = fm.height() + 3; @@ -203,6 +225,54 @@ void TaskView::resizeEvent(QResizeEvent *e) static_cast(itemDelegate())->emitSizeHintChanged(selectionModel()->currentIndex()); } +void TaskView::mousePressEvent(QMouseEvent *e) +{ + m_mouseButtonPressed = e->button(); + ListView::mousePressEvent(e); +} + +void TaskView::mouseReleaseEvent(QMouseEvent *e) +{ + if (m_linksActive && m_mouseButtonPressed == Qt::LeftButton) { + const Location loc = locationForPos(e->pos()); + if (!loc.file.isEmpty()) + Core::EditorManager::openEditorAt(loc.file.toString(), loc.line, loc.column); + } + + // Mouse was released, activate links again + m_linksActive = true; + m_mouseButtonPressed = Qt::NoButton; + ListView::mouseReleaseEvent(e); +} + +void TaskView::mouseMoveEvent(QMouseEvent *e) +{ + // Cursor was dragged, deactivate links + if (m_mouseButtonPressed != Qt::NoButton) + m_linksActive = false; + + viewport()->setCursor(m_linksActive && !locationForPos(e->pos()).file.isEmpty() + ? Qt::PointingHandCursor : Qt::ArrowCursor); + ListView::mouseMoveEvent(e); +} + +TaskView::Location TaskView::locationForPos(const QPoint &pos) +{ + const auto delegate = qobject_cast(itemDelegate(indexAt(pos))); + if (!delegate) + return {}; + Utils::OutputFormatter formatter; + Location loc; + connect(&formatter, &Utils::OutputFormatter::openInEditorRequested, this, + [&loc](const Utils::FilePath &fp, int line, int column) { + loc.file = fp; + loc.line = line; + loc.column = column; + }); + formatter.handleLink(delegate->hrefForPos(pos)); + return loc; +} + ///// // TaskWindow ///// @@ -764,6 +834,15 @@ void TaskDelegate::currentChanged(const QModelIndex ¤t, const QModelIndex emit sizeHintChanged(previous); } +QString TaskDelegate::hrefForPos(const QPointF &pos) +{ + for (const auto &link : m_hrefs) { + if (link.first.contains(pos)) + return link.second; + } + return {}; +} + void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; @@ -825,7 +904,10 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, int height = 0; description.replace(QLatin1Char('\n'), QChar::LineSeparator); QTextLayout tl(description); - tl.setFormats(index.data(TaskModel::Task_t).value().formats); + QVector formats = index.data(TaskModel::Task_t).value().formats; + for (QTextLayout::FormatRange &format : formats) + format.format.setForeground(opt.palette.highlightedText()); + tl.setFormats(formats); tl.beginLayout(); while (true) { QTextLine line = tl.createLine(); @@ -837,7 +919,33 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, height += static_cast(line.height()); } tl.endLayout(); + const QPoint indexPos = view->visualRect(index).topLeft(); tl.draw(painter, QPoint(positions.textAreaLeft(), positions.top())); + m_hrefs.clear(); + for (const auto &range : tl.formats()) { + if (!range.format.isAnchor()) + continue; + const QTextLine &firstLinkLine = tl.lineForTextPosition(range.start); + const QTextLine &lastLinkLine = tl.lineForTextPosition(range.start + range.length - 1); + for (int i = firstLinkLine.lineNumber(); i <= lastLinkLine.lineNumber(); ++i) { + const QTextLine &linkLine = tl.lineAt(i); + if (!linkLine.isValid()) + break; + const QPointF linePos = linkLine.position(); + const int linkStartPos = i == firstLinkLine.lineNumber() + ? range.start : linkLine.textStart(); + const qreal startOffset = linkLine.cursorToX(linkStartPos); + const int linkEndPos = i == lastLinkLine.lineNumber() + ? range.start + range.length + : linkLine.textStart() + linkLine.textLength(); + const qreal endOffset = linkLine.cursorToX(linkEndPos); + const QPointF linkPos(indexPos.x() + positions.textAreaLeft() + linePos.x() + + startOffset, positions.top() + linePos.y()); + const QSize linkSize(endOffset - startOffset, linkLine.height()); + const QRectF linkRect(linkPos, linkSize); + m_hrefs << qMakePair(linkRect, range.format.anchorHref()); + } + } QColor mix; mix.setRgb( static_cast(0.7 * textColor.red() + 0.3 * backgroundColor.red()),