2019-07-22 14:39:01 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2019 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2019-10-01 13:16:17 +02:00
|
|
|
#include "pythonrunconfiguration.h"
|
|
|
|
|
|
2019-07-22 14:39:01 +02:00
|
|
|
#include "pythonconstants.h"
|
2021-12-13 14:19:30 +01:00
|
|
|
#include "pythonlanguageclient.h"
|
2019-07-22 14:39:01 +02:00
|
|
|
#include "pythonproject.h"
|
|
|
|
|
#include "pythonsettings.h"
|
|
|
|
|
|
|
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
|
|
|
|
2019-10-01 13:16:17 +02:00
|
|
|
#include <languageclient/languageclientmanager.h>
|
|
|
|
|
|
2019-11-15 15:44:45 +01:00
|
|
|
#include <projectexplorer/buildsystem.h>
|
2019-07-22 14:39:01 +02:00
|
|
|
#include <projectexplorer/localenvironmentaspect.h>
|
|
|
|
|
#include <projectexplorer/runconfigurationaspects.h>
|
|
|
|
|
#include <projectexplorer/target.h>
|
|
|
|
|
#include <projectexplorer/taskhub.h>
|
|
|
|
|
|
2019-10-01 13:16:17 +02:00
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
|
|
2020-09-18 12:11:40 +02:00
|
|
|
#include <utils/aspects.h>
|
2019-07-22 14:39:01 +02:00
|
|
|
#include <utils/fileutils.h>
|
2020-09-18 12:11:40 +02:00
|
|
|
#include <utils/layoutbuilder.h>
|
2019-07-22 14:39:01 +02:00
|
|
|
#include <utils/outputformatter.h>
|
|
|
|
|
#include <utils/theme/theme.h>
|
|
|
|
|
|
|
|
|
|
#include <QBoxLayout>
|
|
|
|
|
#include <QComboBox>
|
|
|
|
|
#include <QFormLayout>
|
|
|
|
|
#include <QPlainTextEdit>
|
|
|
|
|
#include <QPushButton>
|
|
|
|
|
|
|
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace Python {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
2020-04-14 15:28:44 +02:00
|
|
|
class PythonOutputLineParser : public OutputLineParser
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2020-04-14 15:28:44 +02:00
|
|
|
PythonOutputLineParser()
|
2019-07-22 14:39:01 +02:00
|
|
|
// Note that moc dislikes raw string literals.
|
|
|
|
|
: filePattern("^(\\s*)(File \"([^\"]+)\", line (\\d+), .*$)")
|
|
|
|
|
{
|
|
|
|
|
TaskHub::clearTasks(PythonErrorTaskCategory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2020-04-14 15:28:44 +02:00
|
|
|
Result handleLine(const QString &text, OutputFormat format) final
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
2020-03-18 12:56:35 +01:00
|
|
|
if (!m_inTraceBack) {
|
|
|
|
|
m_inTraceBack = format == StdErrFormat
|
|
|
|
|
&& text.startsWith("Traceback (most recent call last):");
|
2020-04-09 17:47:01 +02:00
|
|
|
if (m_inTraceBack)
|
2020-03-19 16:00:37 +01:00
|
|
|
return Status::InProgress;
|
|
|
|
|
return Status::NotHandled;
|
2020-03-18 12:56:35 +01:00
|
|
|
}
|
|
|
|
|
|
2020-06-26 13:59:38 +02:00
|
|
|
const Utils::Id category(PythonErrorTaskCategory);
|
2020-03-18 12:56:35 +01:00
|
|
|
const QRegularExpressionMatch match = filePattern.match(text);
|
|
|
|
|
if (match.hasMatch()) {
|
2020-04-09 17:47:01 +02:00
|
|
|
const LinkSpec link(match.capturedStart(2), match.capturedLength(2), match.captured(2));
|
2020-03-18 12:56:35 +01:00
|
|
|
const auto fileName = FilePath::fromString(match.captured(3));
|
2020-06-23 13:00:43 +02:00
|
|
|
const int lineNumber = match.captured(4).toInt();
|
2020-03-18 12:56:35 +01:00
|
|
|
m_tasks.append({Task::Warning, QString(), fileName, lineNumber, category});
|
2020-04-09 17:47:01 +02:00
|
|
|
return {Status::InProgress, {link}};
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 16:00:37 +01:00
|
|
|
Status status = Status::InProgress;
|
2020-03-18 12:56:35 +01:00
|
|
|
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});
|
2019-07-22 14:39:01 +02:00
|
|
|
} else {
|
2020-03-18 12:56:35 +01:00
|
|
|
Task &task = m_tasks.back();
|
2020-05-12 16:26:34 +02:00
|
|
|
if (!task.summary.isEmpty())
|
|
|
|
|
task.summary += ' ';
|
|
|
|
|
task.summary += text.trimmed();
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
2020-03-18 12:56:35 +01:00
|
|
|
} 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)
|
2019-07-22 14:39:01 +02:00
|
|
|
TaskHub::addTask(*rit);
|
2020-03-18 12:56:35 +01:00
|
|
|
m_tasks.clear();
|
|
|
|
|
m_inTraceBack = false;
|
2020-03-19 16:00:37 +01:00
|
|
|
status = Status::Done;
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
2020-03-19 16:00:37 +01:00
|
|
|
return status;
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-19 16:00:37 +01:00
|
|
|
bool handleLink(const QString &href) final
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
|
|
|
|
const QRegularExpressionMatch match = filePattern.match(href);
|
|
|
|
|
if (!match.hasMatch())
|
2020-03-19 16:00:37 +01:00
|
|
|
return false;
|
2019-07-22 14:39:01 +02:00
|
|
|
const QString fileName = match.captured(3);
|
2020-06-23 13:00:43 +02:00
|
|
|
const int lineNumber = match.captured(4).toInt();
|
2021-11-01 17:02:02 +01:00
|
|
|
Core::EditorManager::openEditorAt({FilePath::fromString(fileName), lineNumber});
|
2020-03-19 16:00:37 +01:00
|
|
|
return true;
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QRegularExpression filePattern;
|
2020-03-18 12:56:35 +01:00
|
|
|
QList<Task> m_tasks;
|
|
|
|
|
bool m_inTraceBack;
|
2019-07-22 14:39:01 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
|
|
2020-09-18 12:11:40 +02:00
|
|
|
class InterpreterAspect : public BaseAspect
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
InterpreterAspect() = default;
|
|
|
|
|
|
|
|
|
|
Interpreter currentInterpreter() const;
|
|
|
|
|
void updateInterpreters(const QList<Interpreter> &interpreters);
|
|
|
|
|
void setDefaultInterpreter(const Interpreter &interpreter) { m_defaultId = interpreter.id; }
|
|
|
|
|
|
|
|
|
|
void fromMap(const QVariantMap &) override;
|
|
|
|
|
void toMap(QVariantMap &) const override;
|
2019-10-15 17:20:51 +02:00
|
|
|
void addToLayout(LayoutBuilder &builder) override;
|
2019-07-22 14:39:01 +02:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void updateCurrentInterpreter();
|
|
|
|
|
void updateComboBox();
|
|
|
|
|
QList<Interpreter> m_interpreters;
|
|
|
|
|
QPointer<QComboBox> m_comboBox;
|
|
|
|
|
QString m_defaultId;
|
|
|
|
|
QString m_currentId;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Interpreter InterpreterAspect::currentInterpreter() const
|
|
|
|
|
{
|
2021-06-10 10:14:51 +02:00
|
|
|
return Utils::findOrDefault(m_interpreters, Utils::equal(&Interpreter::id, m_currentId));
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterAspect::updateInterpreters(const QList<Interpreter> &interpreters)
|
|
|
|
|
{
|
|
|
|
|
m_interpreters = interpreters;
|
|
|
|
|
if (m_comboBox)
|
|
|
|
|
updateComboBox();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterAspect::fromMap(const QVariantMap &map)
|
|
|
|
|
{
|
|
|
|
|
m_currentId = map.value(settingsKey(), m_defaultId).toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterAspect::toMap(QVariantMap &map) const
|
|
|
|
|
{
|
2021-03-11 16:52:22 +01:00
|
|
|
saveToMap(map, m_currentId, QString(), settingsKey());
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-15 17:20:51 +02:00
|
|
|
void InterpreterAspect::addToLayout(LayoutBuilder &builder)
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
|
|
|
|
if (QTC_GUARD(m_comboBox.isNull()))
|
|
|
|
|
m_comboBox = new QComboBox;
|
|
|
|
|
|
|
|
|
|
updateComboBox();
|
2021-06-22 18:25:56 +02:00
|
|
|
connect(m_comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
2019-10-15 17:20:51 +02:00
|
|
|
this, &InterpreterAspect::updateCurrentInterpreter);
|
2019-07-22 14:39:01 +02:00
|
|
|
|
|
|
|
|
auto manageButton = new QPushButton(tr("Manage..."));
|
|
|
|
|
connect(manageButton, &QPushButton::clicked, []() {
|
|
|
|
|
Core::ICore::showOptionsDialog(Constants::C_PYTHONOPTIONS_PAGE_ID);
|
|
|
|
|
});
|
|
|
|
|
|
2020-09-18 04:54:41 +02:00
|
|
|
builder.addItems({tr("Interpreter"), m_comboBox.data(), manageButton});
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterAspect::updateCurrentInterpreter()
|
|
|
|
|
{
|
2021-06-22 18:25:56 +02:00
|
|
|
const int index = m_comboBox->currentIndex();
|
|
|
|
|
if (index < 0)
|
|
|
|
|
return;
|
|
|
|
|
QTC_ASSERT(index < m_interpreters.size(), return);
|
|
|
|
|
m_currentId = m_interpreters[index].id;
|
|
|
|
|
m_comboBox->setToolTip(m_interpreters[index].command.toUserOutput());
|
2019-07-22 14:39:01 +02:00
|
|
|
emit changed();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InterpreterAspect::updateComboBox()
|
|
|
|
|
{
|
|
|
|
|
int currentIndex = -1;
|
|
|
|
|
int defaultIndex = -1;
|
|
|
|
|
const QString currentId = m_currentId;
|
|
|
|
|
m_comboBox->clear();
|
2021-02-15 10:03:57 +01:00
|
|
|
for (const Interpreter &interpreter : qAsConst(m_interpreters)) {
|
2019-07-22 14:39:01 +02:00
|
|
|
int index = m_comboBox->count();
|
|
|
|
|
m_comboBox->addItem(interpreter.name);
|
|
|
|
|
m_comboBox->setItemData(index, interpreter.command.toUserOutput(), Qt::ToolTipRole);
|
|
|
|
|
if (interpreter.id == currentId)
|
|
|
|
|
currentIndex = index;
|
|
|
|
|
if (interpreter.id == m_defaultId)
|
|
|
|
|
defaultIndex = index;
|
|
|
|
|
}
|
|
|
|
|
if (currentIndex >= 0)
|
|
|
|
|
m_comboBox->setCurrentIndex(currentIndex);
|
|
|
|
|
else if (defaultIndex >= 0)
|
|
|
|
|
m_comboBox->setCurrentIndex(defaultIndex);
|
|
|
|
|
updateCurrentInterpreter();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-13 09:16:00 +02:00
|
|
|
class MainScriptAspect : public StringAspect
|
2019-07-22 14:39:01 +02:00
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
MainScriptAspect() = default;
|
|
|
|
|
};
|
|
|
|
|
|
2020-06-26 13:59:38 +02:00
|
|
|
PythonRunConfiguration::PythonRunConfiguration(Target *target, Utils::Id id)
|
2019-07-22 14:39:01 +02:00
|
|
|
: RunConfiguration(target, id)
|
|
|
|
|
{
|
|
|
|
|
auto interpreterAspect = addAspect<InterpreterAspect>();
|
|
|
|
|
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
2019-10-01 13:16:17 +02:00
|
|
|
connect(interpreterAspect, &InterpreterAspect::changed,
|
|
|
|
|
this, &PythonRunConfiguration::updateLanguageServer);
|
2019-07-22 14:39:01 +02:00
|
|
|
|
|
|
|
|
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
|
|
|
|
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
|
|
|
|
|
2019-10-25 09:15:59 +02:00
|
|
|
QList<Interpreter> interpreters = PythonSettings::detectPythonVenvs(project()->projectDirectory());
|
|
|
|
|
aspect<InterpreterAspect>()->updateInterpreters(PythonSettings::interpreters());
|
|
|
|
|
aspect<InterpreterAspect>()->setDefaultInterpreter(
|
|
|
|
|
interpreters.isEmpty() ? PythonSettings::defaultInterpreter() : interpreters.first());
|
2019-07-22 14:39:01 +02:00
|
|
|
|
2020-08-13 09:16:00 +02:00
|
|
|
auto bufferedAspect = addAspect<BoolAspect>();
|
2020-01-30 09:47:50 +01:00
|
|
|
bufferedAspect->setSettingsKey("PythonEditor.RunConfiguation.Buffered");
|
2020-08-13 09:16:00 +02:00
|
|
|
bufferedAspect->setLabel(tr("Buffered output"), BoolAspect::LabelPlacement::AtCheckBox);
|
2020-01-30 09:47:50 +01:00
|
|
|
bufferedAspect->setToolTip(tr("Enabling improves output performance, "
|
|
|
|
|
"but results in delayed output."));
|
|
|
|
|
|
2019-07-22 14:39:01 +02:00
|
|
|
auto scriptAspect = addAspect<MainScriptAspect>();
|
|
|
|
|
scriptAspect->setSettingsKey("PythonEditor.RunConfiguation.Script");
|
|
|
|
|
scriptAspect->setLabelText(tr("Script:"));
|
2020-08-13 09:16:00 +02:00
|
|
|
scriptAspect->setDisplayStyle(StringAspect::LabelDisplay);
|
2019-07-22 14:39:01 +02:00
|
|
|
|
|
|
|
|
addAspect<LocalEnvironmentAspect>(target);
|
|
|
|
|
|
|
|
|
|
auto argumentsAspect = addAspect<ArgumentsAspect>();
|
|
|
|
|
|
2019-11-14 14:22:28 +01:00
|
|
|
addAspect<WorkingDirectoryAspect>();
|
2019-07-22 14:39:01 +02:00
|
|
|
addAspect<TerminalAspect>();
|
|
|
|
|
|
2020-01-30 09:47:50 +01:00
|
|
|
setCommandLineGetter([this, bufferedAspect, interpreterAspect, argumentsAspect] {
|
|
|
|
|
CommandLine cmd{interpreterAspect->currentInterpreter().command};
|
|
|
|
|
if (!bufferedAspect->value())
|
|
|
|
|
cmd.addArg("-u");
|
|
|
|
|
cmd.addArg(mainScript());
|
2019-07-22 14:39:01 +02:00
|
|
|
cmd.addArgs(argumentsAspect->arguments(macroExpander()), CommandLine::Raw);
|
|
|
|
|
return cmd;
|
|
|
|
|
});
|
|
|
|
|
|
2019-11-25 18:34:51 +01:00
|
|
|
setUpdater([this, scriptAspect] {
|
|
|
|
|
const BuildTargetInfo bti = buildTargetInfo();
|
|
|
|
|
const QString script = bti.targetFilePath.toUserOutput();
|
|
|
|
|
setDefaultDisplayName(tr("Run %1").arg(script));
|
|
|
|
|
scriptAspect->setValue(script);
|
2020-09-16 12:02:49 +02:00
|
|
|
aspect<WorkingDirectoryAspect>()->setDefaultWorkingDirectory(bti.targetFilePath.parentDir());
|
2019-11-25 18:34:51 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update);
|
2019-07-22 14:39:01 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-01 13:16:17 +02:00
|
|
|
void PythonRunConfiguration::updateLanguageServer()
|
|
|
|
|
{
|
|
|
|
|
using namespace LanguageClient;
|
|
|
|
|
|
|
|
|
|
const FilePath python(FilePath::fromUserInput(interpreter()));
|
|
|
|
|
|
|
|
|
|
for (FilePath &file : project()->files(Project::AllFiles)) {
|
|
|
|
|
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
|
|
|
|
if (document->mimeType() == Constants::C_PY_MIMETYPE)
|
2019-10-18 10:31:14 +02:00
|
|
|
PyLSConfigureAssistant::instance()->openDocumentWithPython(python, document);
|
2019-10-01 13:16:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-22 14:39:01 +02:00
|
|
|
bool PythonRunConfiguration::supportsDebugger() const
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString PythonRunConfiguration::mainScript() const
|
|
|
|
|
{
|
|
|
|
|
return aspect<MainScriptAspect>()->value();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString PythonRunConfiguration::arguments() const
|
|
|
|
|
{
|
|
|
|
|
return aspect<ArgumentsAspect>()->arguments(macroExpander());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString PythonRunConfiguration::interpreter() const
|
|
|
|
|
{
|
|
|
|
|
return aspect<InterpreterAspect>()->currentInterpreter().command.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PythonRunConfigurationFactory::PythonRunConfigurationFactory()
|
|
|
|
|
{
|
|
|
|
|
registerRunConfiguration<PythonRunConfiguration>("PythonEditor.RunConfiguration.");
|
|
|
|
|
addSupportedProjectType(PythonProjectId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PythonOutputFormatterFactory::PythonOutputFormatterFactory()
|
|
|
|
|
{
|
2020-04-22 10:46:09 +02:00
|
|
|
setFormatterCreator([](Target *t) -> QList<OutputLineParser *> {
|
2020-04-22 10:20:36 +02:00
|
|
|
if (t && t->project()->mimeType() == Constants::C_PY_MIMETYPE)
|
2020-04-22 10:46:09 +02:00
|
|
|
return {new PythonOutputLineParser};
|
|
|
|
|
return {};
|
2019-07-22 14:39:01 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Python
|
|
|
|
|
|
|
|
|
|
#include "pythonrunconfiguration.moc"
|