// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "pythonrunconfiguration.h" #include "pyside.h" #include "pythonbuildconfiguration.h" #include "pythonconstants.h" #include "pythoneditor.h" #include "pythonkitaspect.h" #include "pythonlanguageclient.h" #include "pythonproject.h" #include "pythonsettings.h" #include "pythontr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace Python::Internal { class PythonOutputLineParser : public OutputLineParser { public: PythonOutputLineParser() // Note that moc dislikes raw string literals. : filePattern("^(\\s*)(File \"([^\"]+)\", line (\\d+), .*$)") { TaskHub::clearTasks(PythonErrorTaskCategory); } private: Result handleLine(const QString &text, OutputFormat format) final { if (!m_inTraceBack) { m_inTraceBack = format == StdErrFormat && text.startsWith("Traceback (most recent call last):"); if (m_inTraceBack) return Status::InProgress; return Status::NotHandled; } const Id category(PythonErrorTaskCategory); const QRegularExpressionMatch match = filePattern.match(text); if (match.hasMatch()) { const LinkSpec link(match.capturedStart(2), match.capturedLength(2), match.captured(2)); const auto fileName = FilePath::fromString(match.captured(3)); const int lineNumber = match.captured(4).toInt(); m_tasks.append({Task::Warning, QString(), fileName, lineNumber, category}); return {Status::InProgress, {link}}; } Status status = Status::InProgress; if (text.startsWith(' ')) { // Neither traceback start, nor file, nor error message line. // Not sure if that can actually happen. if (m_tasks.isEmpty()) { m_tasks.append({Task::Warning, text.trimmed(), {}, -1, category}); } else { Task &task = m_tasks.back(); if (!task.summary.isEmpty()) task.summary += ' '; task.summary += text.trimmed(); } } else { // The actual exception. This ends the traceback. TaskHub::addTask({Task::Error, text, {}, -1, category}); for (auto rit = m_tasks.crbegin(), rend = m_tasks.crend(); rit != rend; ++rit) TaskHub::addTask(*rit); m_tasks.clear(); m_inTraceBack = false; status = Status::Done; } return status; } bool handleLink(const QString &href) final { const QRegularExpressionMatch match = filePattern.match(href); if (!match.hasMatch()) return false; const QString fileName = match.captured(3); const int lineNumber = match.captured(4).toInt(); Core::EditorManager::openEditorAt({FilePath::fromString(fileName), lineNumber}); return true; } const QRegularExpression filePattern; QList m_tasks; bool m_inTraceBack; }; // RunConfiguration class PythonRunConfiguration : public RunConfiguration { public: PythonRunConfiguration(Target *target, Id id) : RunConfiguration(target, id) { buffered.setSettingsKey("PythonEditor.RunConfiguation.Buffered"); buffered.setLabelText(Tr::tr("Buffered output")); buffered.setLabelPlacement(BoolAspect::LabelPlacement::AtCheckBox); buffered.setToolTip(Tr::tr("Enabling improves output performance, " "but results in delayed output.")); mainScript.setSettingsKey("PythonEditor.RunConfiguation.Script"); mainScript.setLabelText(Tr::tr("Script:")); mainScript.setReadOnly(true); environment.setSupportForBuildEnvironment(target); arguments.setMacroExpander(macroExpander()); workingDir.setMacroExpander(macroExpander()); x11Forwarding.setMacroExpander(macroExpander()); x11Forwarding.setVisible(HostOsInfo::isAnyUnixHost()); interpreter.setLabelText(Tr::tr("Python:")); interpreter.setReadOnly(true); setCommandLineGetter([this] { CommandLine cmd; cmd.setExecutable(interpreter()); if (interpreter().isEmpty()) return cmd; if (!buffered()) cmd.addArg("-u"); cmd.addArg(mainScript().fileName()); cmd.addArgs(arguments(), CommandLine::Raw); return cmd; }); setUpdater([this] { const BuildTargetInfo bti = buildTargetInfo(); const auto python = FilePath::fromSettings(bti.additionalData.toMap().value("python")); interpreter.setValue(python); setDefaultDisplayName(Tr::tr("Run %1").arg(bti.targetFilePath.toUserOutput())); mainScript.setValue(bti.targetFilePath); workingDir.setDefaultWorkingDirectory(bti.targetFilePath.parentDir()); }); connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); } FilePathAspect interpreter{this}; BoolAspect buffered{this}; MainScriptAspect mainScript{this}; EnvironmentAspect environment{this}; ArgumentsAspect arguments{this}; WorkingDirectoryAspect workingDir{this}; TerminalAspect terminal{this}; X11ForwardingAspect x11Forwarding{this}; }; // Factories PythonRunConfigurationFactory::PythonRunConfigurationFactory() { registerRunConfiguration(Constants::C_PYTHONRUNCONFIGURATION_ID); addSupportedProjectType(PythonProjectId); } void setupPythonOutputParser() { addOutputParserFactory([](Target *t) -> OutputLineParser * { if (t && t->project()->mimeType() == Constants::C_PY_PROJECT_MIME_TYPE) return new PythonOutputLineParser; return nullptr; }); } } // Python::Internal