diff --git a/src/plugins/pythoneditor/pythoneditorplugin.cpp b/src/plugins/pythoneditor/pythoneditorplugin.cpp index 6331d7ef52b..73996e939dd 100644 --- a/src/plugins/pythoneditor/pythoneditorplugin.cpp +++ b/src/plugins/pythoneditor/pythoneditorplugin.cpp @@ -48,10 +48,13 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -60,6 +63,9 @@ #include #include #include +#include +#include +#include using namespace Core; using namespace ProjectExplorer; @@ -74,6 +80,7 @@ const char InterpreterKey[] = "PythonEditor.RunConfiguation.Interpreter"; const char MainScriptKey[] = "PythonEditor.RunConfiguation.MainScript"; const char PythonMimeType[] = "text/x-python-project"; // ### FIXME const char PythonProjectId[] = "PythonProject"; +const char PythonErrorTaskCategory[] = "Task.Category.Python"; class PythonRunConfiguration; class PythonProjectFile; @@ -149,6 +156,7 @@ public: QWidget *createConfigurationWidget() override; QVariantMap toMap() const override; bool fromMap(const QVariantMap &map) override; + OutputFormatter *createOutputFormatter() const override; Runnable runnable() const override; void doAdditionalSetup(const RunConfigurationCreationInfo &info) override; @@ -224,6 +232,93 @@ Runnable PythonRunConfiguration::runnable() const return r; } +static QTextCharFormat linkFormat(const QTextCharFormat &inputFormat, const QString &href) +{ + QTextCharFormat result = inputFormat; + result.setForeground(creatorTheme()->color(Theme::TextColorLink)); + result.setUnderlineStyle(QTextCharFormat::SingleUnderline); + result.setAnchor(true); + result.setAnchorHref(href); + return result; +} + +class PythonOutputFormatter : public OutputFormatter +{ +public: + PythonOutputFormatter() + : filePattern(R"RX(^(\s*)(File "([^"]+)", line (\d+), .*$))RX") + {} + +private: + void appendMessage(const QString &text, OutputFormat format) final + { + const bool isTrace = (format == StdErrFormat + || format == StdErrFormatSameLine) + && (text.startsWith("Traceback (most recent call last):") + || text.startsWith("\nTraceback (most recent call last):")); + + if (!isTrace) { + OutputFormatter::appendMessage(text, format); + return; + } + + const QTextCharFormat frm = charFormat(format); + const Core::Id id(PythonErrorTaskCategory); + QVector tasks; + const QStringList lines = text.split('\n'); + unsigned taskId = unsigned(lines.size()); + + for (const QString &line : lines) { + const QRegularExpressionMatch match = filePattern.match(line); + if (match.hasMatch()) { + QTextCursor tc = plainTextEdit()->textCursor(); + tc.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + tc.insertText('\n' + match.captured(1)); + tc.insertText(match.captured(2), linkFormat(frm, match.captured(2))); + + const auto fileName = FileName::fromString(match.captured(3)); + const int lineNumber = match.capturedRef(4).toInt(); + Task task(Task::Warning, + QString(), fileName, lineNumber, id); + task.taskId = --taskId; + tasks.append(task); + } else { + if (!tasks.isEmpty()) { + Task &task = tasks.back(); + if (!task.description.isEmpty()) + task.description += ' '; + task.description += line.trimmed(); + } + OutputFormatter::appendMessage('\n' + line, format); + } + } + if (!tasks.isEmpty()) { + tasks.back().type = Task::Error; + for (auto rit = tasks.crbegin(), rend = tasks.crend(); rit != rend; ++rit) + TaskHub::addTask(*rit); + } + } + + void handleLink(const QString &href) final + { + const QRegularExpressionMatch match = filePattern.match(href); + if (!match.hasMatch()) + return; + const QString fileName = match.captured(3); + const int lineNumber = match.capturedRef(4).toInt(); + Core::EditorManager::openEditorAt(fileName, lineNumber); + } + + QPointer project; + const QRegularExpression filePattern; +}; + +OutputFormatter *PythonRunConfiguration::createOutputFormatter() const +{ + TaskHub::clearTasks(PythonErrorTaskCategory); + return new PythonOutputFormatter; +} + QString PythonRunConfiguration::arguments() const { auto aspect = extraAspect(); @@ -600,6 +695,8 @@ void PythonEditorPlugin::extensionsInitialized() const QIcon icon = QIcon::fromTheme(C_PY_MIME_ICON); if (!icon.isNull()) Core::FileIconProvider::registerIconOverlayForMimeType(icon, C_PY_MIMETYPE); + + ProjectExplorer::TaskHub::instance()->addCategory(PythonErrorTaskCategory, "Python", true); } } // namespace Internal