From 217f2a49f6be36a33c986ead20333fb6c6b2ab97 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Thu, 29 Mar 2018 15:29:17 +0200 Subject: PythonEditor: Show python stack traces in the build issues pane Add an output formatter that captures stack traces and adds them as tasks. This helps to speed up fixing syntax errors. Change-Id: I8a4fa77d0f87b4d16f4bb780b15ec06154a52441 Reviewed-by: Friedemann Kleint Reviewed-by: hjk --- src/plugins/pythoneditor/pythoneditorplugin.cpp | 97 +++++++++++++++++++++++++ 1 file changed, 97 insertions(+) (limited to 'src/plugins/pythoneditor') diff --git a/src/plugins/pythoneditor/pythoneditorplugin.cpp b/src/plugins/pythoneditor/pythoneditorplugin.cpp index 6331d7ef52..73996e939d 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 -- cgit v1.2.1