forked from qt-creator/qt-creator
Python: Switch pyls on interpreter change
Change-Id: I458b635986a55003a1e7254e27e2df9667704273 Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -611,6 +611,13 @@ QString StdIOSettings::arguments() const
|
|||||||
return Utils::globalMacroExpander()->expand(m_arguments);
|
return Utils::globalMacroExpander()->expand(m_arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Utils::CommandLine StdIOSettings::command() const
|
||||||
|
{
|
||||||
|
return Utils::CommandLine(Utils::FilePath::fromUserInput(m_executable),
|
||||||
|
arguments(),
|
||||||
|
Utils::CommandLine::Raw);
|
||||||
|
}
|
||||||
|
|
||||||
BaseClientInterface *StdIOSettings::createInterface() const
|
BaseClientInterface *StdIOSettings::createInterface() const
|
||||||
{
|
{
|
||||||
return new StdIOClientInterface(m_executable, arguments());
|
return new StdIOClientInterface(m_executable, arguments());
|
||||||
|
@@ -29,6 +29,8 @@
|
|||||||
|
|
||||||
#include <coreplugin/dialogs/ioptionspage.h>
|
#include <coreplugin/dialogs/ioptionspage.h>
|
||||||
|
|
||||||
|
#include <utils/fileutils.h>
|
||||||
|
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
@@ -119,6 +121,7 @@ public:
|
|||||||
QVariantMap toMap() const override;
|
QVariantMap toMap() const override;
|
||||||
void fromMap(const QVariantMap &map) override;
|
void fromMap(const QVariantMap &map) override;
|
||||||
QString arguments() const;
|
QString arguments() const;
|
||||||
|
Utils::CommandLine command() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BaseClientInterface *createInterface() const override;
|
BaseClientInterface *createInterface() const override;
|
||||||
|
@@ -12,4 +12,5 @@ add_qtc_plugin(Python
|
|||||||
pythonrunconfiguration.cpp pythonrunconfiguration.h
|
pythonrunconfiguration.cpp pythonrunconfiguration.h
|
||||||
pythonsettings.cpp pythonsettings.h
|
pythonsettings.cpp pythonsettings.h
|
||||||
pythonscanner.cpp pythonscanner.h
|
pythonscanner.cpp pythonscanner.h
|
||||||
|
pythonutils.cpp pythonutils.h
|
||||||
)
|
)
|
||||||
|
@@ -13,7 +13,8 @@ HEADERS += \
|
|||||||
pythonproject.h \
|
pythonproject.h \
|
||||||
pythonrunconfiguration.h \
|
pythonrunconfiguration.h \
|
||||||
pythonscanner.h \
|
pythonscanner.h \
|
||||||
pythonsettings.h
|
pythonsettings.h \
|
||||||
|
pythonutils.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
pythonplugin.cpp \
|
pythonplugin.cpp \
|
||||||
@@ -23,7 +24,8 @@ SOURCES += \
|
|||||||
pythonproject.cpp \
|
pythonproject.cpp \
|
||||||
pythonrunconfiguration.cpp \
|
pythonrunconfiguration.cpp \
|
||||||
pythonscanner.cpp \
|
pythonscanner.cpp \
|
||||||
pythonsettings.cpp
|
pythonsettings.cpp \
|
||||||
|
pythonutils.cpp
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
python.qrc
|
python.qrc
|
||||||
|
@@ -33,6 +33,8 @@ QtcPlugin {
|
|||||||
"pythonscanner.cpp",
|
"pythonscanner.cpp",
|
||||||
"pythonsettings.cpp",
|
"pythonsettings.cpp",
|
||||||
"pythonsettings.h",
|
"pythonsettings.h",
|
||||||
|
"pythonutils.cpp",
|
||||||
|
"pythonutils.h",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,327 +27,22 @@
|
|||||||
#include "pythonconstants.h"
|
#include "pythonconstants.h"
|
||||||
#include "pythonhighlighter.h"
|
#include "pythonhighlighter.h"
|
||||||
#include "pythonindenter.h"
|
#include "pythonindenter.h"
|
||||||
#include "pythonplugin.h"
|
#include "pythonutils.h"
|
||||||
#include "pythonproject.h"
|
|
||||||
#include "pythonrunconfiguration.h"
|
|
||||||
#include "pythonsettings.h"
|
|
||||||
|
|
||||||
#include <coreplugin/infobar.h>
|
|
||||||
#include <coreplugin/progressmanager/progressmanager.h>
|
|
||||||
|
|
||||||
#include <languageclient/client.h>
|
|
||||||
#include <languageclient/languageclientinterface.h>
|
|
||||||
#include <languageclient/languageclientmanager.h>
|
|
||||||
|
|
||||||
#include <projectexplorer/session.h>
|
|
||||||
#include <projectexplorer/target.h>
|
|
||||||
|
|
||||||
#include <texteditor/textdocument.h>
|
#include <texteditor/textdocument.h>
|
||||||
#include <texteditor/texteditoractionhandler.h>
|
#include <texteditor/texteditoractionhandler.h>
|
||||||
#include <texteditor/texteditorconstants.h>
|
|
||||||
|
|
||||||
#include <utils/executeondestruction.h>
|
|
||||||
#include <utils/qtcassert.h>
|
|
||||||
#include <utils/synchronousprocess.h>
|
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QFutureWatcher>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
using namespace ProjectExplorer;
|
|
||||||
using namespace Utils;
|
|
||||||
|
|
||||||
namespace Python {
|
namespace Python {
|
||||||
namespace Internal {
|
namespace Internal {
|
||||||
|
|
||||||
static constexpr char startPylsInfoBarId[] = "PythonEditor::StartPyls";
|
|
||||||
static constexpr char installPylsInfoBarId[] = "PythonEditor::InstallPyls";
|
|
||||||
static constexpr char installPylsTaskId[] = "PythonEditor::InstallPylsTask";
|
|
||||||
|
|
||||||
struct PythonForProject
|
|
||||||
{
|
|
||||||
FilePath path;
|
|
||||||
PythonProject *project = nullptr;
|
|
||||||
|
|
||||||
QString name() const
|
|
||||||
{
|
|
||||||
if (!path.exists())
|
|
||||||
return {};
|
|
||||||
if (cachedName.first != path) {
|
|
||||||
SynchronousProcess pythonProcess;
|
|
||||||
const CommandLine pythonVersionCommand(path, {"--version"});
|
|
||||||
SynchronousProcessResponse response = pythonProcess.runBlocking(pythonVersionCommand);
|
|
||||||
cachedName.first = path;
|
|
||||||
cachedName.second = response.allOutput().trimmed();
|
|
||||||
}
|
|
||||||
return cachedName.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
mutable QPair<FilePath, QString> cachedName;
|
|
||||||
};
|
|
||||||
|
|
||||||
static PythonForProject detectPython(TextEditor::TextDocument *document)
|
|
||||||
{
|
|
||||||
PythonForProject python;
|
|
||||||
|
|
||||||
python.project = qobject_cast<PythonProject *>(
|
|
||||||
SessionManager::projectForFile(document->filePath()));
|
|
||||||
if (!python.project)
|
|
||||||
python.project = qobject_cast<PythonProject *>(SessionManager::startupProject());
|
|
||||||
|
|
||||||
if (python.project) {
|
|
||||||
if (auto target = python.project->activeTarget()) {
|
|
||||||
if (auto runConfig = qobject_cast<PythonRunConfiguration *>(
|
|
||||||
target->activeRunConfiguration())) {
|
|
||||||
python.path = FilePath::fromString(runConfig->interpreter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!python.path.exists())
|
|
||||||
python.path = PythonSettings::defaultInterpreter().command;
|
|
||||||
|
|
||||||
if (!python.path.exists() && !PythonSettings::interpreters().isEmpty())
|
|
||||||
python.path = PythonSettings::interpreters().first().command;
|
|
||||||
|
|
||||||
return python;
|
|
||||||
}
|
|
||||||
|
|
||||||
FilePath getPylsModulePath(CommandLine pylsCommand)
|
|
||||||
{
|
|
||||||
pylsCommand.addArg("-h");
|
|
||||||
SynchronousProcess pythonProcess;
|
|
||||||
pythonProcess.setEnvironment(pythonProcess.environment() + QStringList("PYTHONVERBOSE=x"));
|
|
||||||
SynchronousProcessResponse response = pythonProcess.runBlocking(pylsCommand);
|
|
||||||
|
|
||||||
static const QString pylsInitPattern = "(.*)"
|
|
||||||
+ QRegularExpression::escape(
|
|
||||||
QDir::toNativeSeparators("/pyls/__init__.py"))
|
|
||||||
+ '$';
|
|
||||||
static const QRegularExpression regexCached(" matches " + pylsInitPattern,
|
|
||||||
QRegularExpression::MultilineOption);
|
|
||||||
static const QRegularExpression regexNotCached(" code object from " + pylsInitPattern,
|
|
||||||
QRegularExpression::MultilineOption);
|
|
||||||
|
|
||||||
const QString &output = response.allOutput();
|
|
||||||
for (auto regex : {regexCached, regexNotCached}) {
|
|
||||||
QRegularExpressionMatch result = regex.match(output);
|
|
||||||
if (result.hasMatch())
|
|
||||||
return FilePath::fromUserInput(result.captured(1));
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PythonLanguageServerState
|
|
||||||
{
|
|
||||||
enum { CanNotBeInstalled, CanBeInstalled, AlreadyInstalled, AlreadyConfigured } state;
|
|
||||||
FilePath pylsModulePath;
|
|
||||||
};
|
|
||||||
|
|
||||||
static QList<const LanguageClient::StdIOSettings *> configuredPythonLanguageServer(
|
|
||||||
Core::IDocument *doc)
|
|
||||||
{
|
|
||||||
using namespace LanguageClient;
|
|
||||||
QList<const StdIOSettings *> result;
|
|
||||||
for (const BaseSettings *setting : LanguageClientManager::currentSettings()) {
|
|
||||||
if (setting->m_languageFilter.isSupported(doc))
|
|
||||||
result << dynamic_cast<const StdIOSettings *>(setting);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PythonLanguageServerState checkPythonLanguageServer(const FilePath &python,
|
|
||||||
TextEditor::TextDocument *document)
|
|
||||||
{
|
|
||||||
using namespace LanguageClient;
|
|
||||||
SynchronousProcess pythonProcess;
|
|
||||||
const CommandLine pythonLShelpCommand(python, {"-m", "pyls", "-h"});
|
|
||||||
SynchronousProcessResponse response = pythonProcess.runBlocking(pythonLShelpCommand);
|
|
||||||
if (response.allOutput().contains("Python Language Server")) {
|
|
||||||
const FilePath &modulePath = getPylsModulePath(pythonLShelpCommand);
|
|
||||||
for (const StdIOSettings *serverSetting : configuredPythonLanguageServer(document)) {
|
|
||||||
CommandLine serverCommand(FilePath::fromUserInput(serverSetting->m_executable),
|
|
||||||
serverSetting->arguments(),
|
|
||||||
CommandLine::Raw);
|
|
||||||
|
|
||||||
if (modulePath == getPylsModulePath(serverCommand))
|
|
||||||
return {PythonLanguageServerState::AlreadyConfigured, FilePath()};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {PythonLanguageServerState::AlreadyInstalled, getPylsModulePath(pythonLShelpCommand)};
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommandLine pythonPipVersionCommand(python, {"-m", "pip", "-V"});
|
|
||||||
response = pythonProcess.runBlocking(pythonPipVersionCommand);
|
|
||||||
if (response.allOutput().startsWith("pip "))
|
|
||||||
return {PythonLanguageServerState::CanBeInstalled, FilePath()};
|
|
||||||
else
|
|
||||||
return {PythonLanguageServerState::CanNotBeInstalled, FilePath()};
|
|
||||||
}
|
|
||||||
|
|
||||||
static LanguageClient::Client *registerLanguageServer(const PythonForProject &python)
|
|
||||||
{
|
|
||||||
auto *settings = new LanguageClient::StdIOSettings();
|
|
||||||
settings->m_executable = python.path.toString();
|
|
||||||
settings->m_arguments = "-m pyls";
|
|
||||||
settings->m_name = PythonEditorFactory::tr("Python Language Server (%1)").arg(python.name());
|
|
||||||
settings->m_languageFilter.mimeTypes = QStringList(Constants::C_PY_MIMETYPE);
|
|
||||||
LanguageClient::LanguageClientManager::registerClientSettings(settings);
|
|
||||||
return LanguageClient::LanguageClientManager::clientForSetting(settings).value(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PythonLSInstallHelper : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
PythonLSInstallHelper(const PythonForProject &python, QPointer<TextEditor::TextDocument> document)
|
|
||||||
: m_python(python)
|
|
||||||
, m_document(document)
|
|
||||||
{
|
|
||||||
m_watcher.setFuture(m_future.future());
|
|
||||||
}
|
|
||||||
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
Core::ProgressManager::addTask(m_future.future(), "Install PyLS", installPylsTaskId);
|
|
||||||
connect(&m_process,
|
|
||||||
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
|
||||||
this,
|
|
||||||
&PythonLSInstallHelper::installFinished);
|
|
||||||
connect(&m_process,
|
|
||||||
&QProcess::readyReadStandardError,
|
|
||||||
this,
|
|
||||||
&PythonLSInstallHelper::errorAvailable);
|
|
||||||
connect(&m_process,
|
|
||||||
&QProcess::readyReadStandardOutput,
|
|
||||||
this,
|
|
||||||
&PythonLSInstallHelper::outputAvailable);
|
|
||||||
|
|
||||||
connect(&m_killTimer, &QTimer::timeout, this, &PythonLSInstallHelper::cancel);
|
|
||||||
connect(&m_watcher, &QFutureWatcher<void>::canceled, this, &PythonLSInstallHelper::cancel);
|
|
||||||
|
|
||||||
// on windows the pyls 0.28.3 crashes with pylint so just install the pyflakes linter
|
|
||||||
const QString &pylsVersion = HostOsInfo::isWindowsHost()
|
|
||||||
? QString{"python-language-server[pyflakes]"}
|
|
||||||
: QString{"python-language-server[all]"};
|
|
||||||
|
|
||||||
m_process.start(m_python.path.toString(),
|
|
||||||
{"-m", "pip", "install", pylsVersion});
|
|
||||||
|
|
||||||
Core::MessageManager::write(tr("Running '%1 %2' to install python language server")
|
|
||||||
.arg(m_process.program(), m_process.arguments().join(' ')));
|
|
||||||
|
|
||||||
m_killTimer.setSingleShot(true);
|
|
||||||
m_killTimer.start(5 /*minutes*/ * 60 * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void cancel()
|
|
||||||
{
|
|
||||||
SynchronousProcess::stopProcess(m_process);
|
|
||||||
Core::MessageManager::write(
|
|
||||||
tr("The Python language server installation canceled by %1.")
|
|
||||||
.arg(m_killTimer.isActive() ? tr("user") : tr("time out")));
|
|
||||||
}
|
|
||||||
|
|
||||||
void installFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
|
||||||
{
|
|
||||||
m_future.reportFinished();
|
|
||||||
if (exitStatus == QProcess::NormalExit && exitCode == 0) {
|
|
||||||
if (LanguageClient::Client *client = registerLanguageServer(m_python))
|
|
||||||
LanguageClient::LanguageClientManager::reOpenDocumentWithClient(m_document, client);
|
|
||||||
} else {
|
|
||||||
Core::MessageManager::write(
|
|
||||||
tr("Installing the Python language server failed with exit code %1").arg(exitCode));
|
|
||||||
}
|
|
||||||
deleteLater();
|
|
||||||
}
|
|
||||||
void outputAvailable()
|
|
||||||
{
|
|
||||||
const QString &stdOut = QString::fromLocal8Bit(m_process.readAllStandardOutput().trimmed());
|
|
||||||
if (!stdOut.isEmpty())
|
|
||||||
Core::MessageManager::write(stdOut);
|
|
||||||
}
|
|
||||||
|
|
||||||
void errorAvailable()
|
|
||||||
{
|
|
||||||
const QString &stdErr = QString::fromLocal8Bit(m_process.readAllStandardError().trimmed());
|
|
||||||
if (!stdErr.isEmpty())
|
|
||||||
Core::MessageManager::write(stdErr);
|
|
||||||
}
|
|
||||||
|
|
||||||
QFutureInterface<void> m_future;
|
|
||||||
QFutureWatcher<void> m_watcher;
|
|
||||||
QProcess m_process;
|
|
||||||
QTimer m_killTimer;
|
|
||||||
const PythonForProject m_python;
|
|
||||||
QPointer<TextEditor::TextDocument> m_document;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void installPythonLanguageServer(const PythonForProject &python,
|
|
||||||
QPointer<TextEditor::TextDocument> document)
|
|
||||||
{
|
|
||||||
document->infoBar()->removeInfo(installPylsInfoBarId);
|
|
||||||
|
|
||||||
auto install = new PythonLSInstallHelper(python, document);
|
|
||||||
install->run();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setupPythonLanguageServer(const PythonForProject &python,
|
|
||||||
QPointer<TextEditor::TextDocument> document)
|
|
||||||
{
|
|
||||||
document->infoBar()->removeInfo(startPylsInfoBarId);
|
|
||||||
if (LanguageClient::Client *client = registerLanguageServer(python))
|
|
||||||
LanguageClient::LanguageClientManager::reOpenDocumentWithClient(document, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void updateEditorInfoBar(const PythonForProject &python, TextEditor::TextDocument *document)
|
|
||||||
{
|
|
||||||
const PythonLanguageServerState &lsState = checkPythonLanguageServer(python.path, document);
|
|
||||||
|
|
||||||
if (lsState.state == PythonLanguageServerState::CanNotBeInstalled
|
|
||||||
|| lsState.state == PythonLanguageServerState::AlreadyConfigured) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Core::InfoBar *infoBar = document->infoBar();
|
|
||||||
if (lsState.state == PythonLanguageServerState::CanBeInstalled
|
|
||||||
&& infoBar->canInfoBeAdded(installPylsInfoBarId)) {
|
|
||||||
auto message
|
|
||||||
= PythonEditorFactory::tr(
|
|
||||||
"Install and set up Python language server (PyLS) for %1 (%2). "
|
|
||||||
"The language server provides Python specific completions and annotations.")
|
|
||||||
.arg(python.name(), python.path.toUserOutput());
|
|
||||||
Core::InfoBarEntry info(installPylsInfoBarId,
|
|
||||||
message,
|
|
||||||
Core::InfoBarEntry::GlobalSuppression::Enabled);
|
|
||||||
info.setCustomButtonInfo(TextEditor::BaseTextEditor::tr("Install"),
|
|
||||||
[=]() { installPythonLanguageServer(python, document); });
|
|
||||||
infoBar->addInfo(info);
|
|
||||||
} else if (lsState.state == PythonLanguageServerState::AlreadyInstalled
|
|
||||||
&& infoBar->canInfoBeAdded(startPylsInfoBarId)) {
|
|
||||||
auto message = PythonEditorFactory::tr("Found a Python language server for %1 (%2). "
|
|
||||||
"Should this one be set up for this document?")
|
|
||||||
.arg(python.name(), python.path.toUserOutput());
|
|
||||||
Core::InfoBarEntry info(startPylsInfoBarId,
|
|
||||||
message,
|
|
||||||
Core::InfoBarEntry::GlobalSuppression::Enabled);
|
|
||||||
info.setCustomButtonInfo(TextEditor::BaseTextEditor::tr("Setup"),
|
|
||||||
[=]() { setupPythonLanguageServer(python, document); });
|
|
||||||
infoBar->addInfo(info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void documentOpened(Core::IDocument *document)
|
static void documentOpened(Core::IDocument *document)
|
||||||
{
|
{
|
||||||
auto textDocument = qobject_cast<TextEditor::TextDocument *>(document);
|
auto textDocument = qobject_cast<TextEditor::TextDocument *>(document);
|
||||||
if (!textDocument || textDocument->mimeType() != Constants::C_PY_MIMETYPE)
|
if (!textDocument || textDocument->mimeType() != Constants::C_PY_MIMETYPE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const PythonForProject &python = detectPython(textDocument);
|
const Utils::FilePath &python = detectPython(textDocument->filePath());
|
||||||
if (!python.path.exists())
|
if (!python.exists())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
updateEditorInfoBar(python, textDocument);
|
updateEditorInfoBar(python, textDocument);
|
||||||
@@ -368,7 +63,7 @@ PythonEditorFactory::PythonEditorFactory()
|
|||||||
setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_PYTHONEDITOR_ID); });
|
setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_PYTHONEDITOR_ID); });
|
||||||
setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); });
|
setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); });
|
||||||
setSyntaxHighlighterCreator([] { return new PythonHighlighter; });
|
setSyntaxHighlighterCreator([] { return new PythonHighlighter; });
|
||||||
setCommentDefinition(CommentDefinition::HashStyle);
|
setCommentDefinition(Utils::CommentDefinition::HashStyle);
|
||||||
setParenthesesMatchingEnabled(true);
|
setParenthesesMatchingEnabled(true);
|
||||||
setCodeFoldingSupported(true);
|
setCodeFoldingSupported(true);
|
||||||
|
|
||||||
@@ -378,5 +73,3 @@ PythonEditorFactory::PythonEditorFactory()
|
|||||||
|
|
||||||
} // namespace Internal
|
} // namespace Internal
|
||||||
} // namespace Python
|
} // namespace Python
|
||||||
|
|
||||||
#include "pythoneditor.moc"
|
|
||||||
|
@@ -23,20 +23,26 @@
|
|||||||
**
|
**
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "pythonrunconfiguration.h"
|
||||||
|
|
||||||
#include "pythonconstants.h"
|
#include "pythonconstants.h"
|
||||||
#include "pythonproject.h"
|
#include "pythonproject.h"
|
||||||
#include "pythonrunconfiguration.h"
|
|
||||||
#include "pythonsettings.h"
|
#include "pythonsettings.h"
|
||||||
|
#include "pythonutils.h"
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
|
|
||||||
|
#include <languageclient/languageclientmanager.h>
|
||||||
|
|
||||||
#include <projectexplorer/localenvironmentaspect.h>
|
#include <projectexplorer/localenvironmentaspect.h>
|
||||||
#include <projectexplorer/projectconfigurationaspects.h>
|
#include <projectexplorer/projectconfigurationaspects.h>
|
||||||
#include <projectexplorer/runconfigurationaspects.h>
|
#include <projectexplorer/runconfigurationaspects.h>
|
||||||
#include <projectexplorer/target.h>
|
#include <projectexplorer/target.h>
|
||||||
#include <projectexplorer/taskhub.h>
|
#include <projectexplorer/taskhub.h>
|
||||||
|
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
#include <utils/outputformatter.h>
|
#include <utils/outputformatter.h>
|
||||||
#include <utils/theme/theme.h>
|
#include <utils/theme/theme.h>
|
||||||
@@ -248,6 +254,8 @@ PythonRunConfiguration::PythonRunConfiguration(Target *target, Core::Id id)
|
|||||||
{
|
{
|
||||||
auto interpreterAspect = addAspect<InterpreterAspect>();
|
auto interpreterAspect = addAspect<InterpreterAspect>();
|
||||||
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
interpreterAspect->setSettingsKey("PythonEditor.RunConfiguation.Interpreter");
|
||||||
|
connect(interpreterAspect, &InterpreterAspect::changed,
|
||||||
|
this, &PythonRunConfiguration::updateLanguageServer);
|
||||||
|
|
||||||
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
connect(PythonSettings::instance(), &PythonSettings::interpretersChanged,
|
||||||
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
interpreterAspect, &InterpreterAspect::updateInterpreters);
|
||||||
@@ -283,6 +291,33 @@ void PythonRunConfiguration::doAdditionalSetup(const RunConfigurationCreationInf
|
|||||||
updateTargetInformation();
|
updateTargetInformation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PythonRunConfiguration::updateLanguageServer()
|
||||||
|
{
|
||||||
|
using namespace LanguageClient;
|
||||||
|
|
||||||
|
const FilePath python(FilePath::fromUserInput(interpreter()));
|
||||||
|
|
||||||
|
if (const StdIOSettings *lsSetting = languageServerForPython(python)) {
|
||||||
|
if (Client *client = LanguageClientManager::clientForSetting(lsSetting).value(0)) {
|
||||||
|
for (FilePath &file : project()->files(Project::AllFiles)) {
|
||||||
|
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
||||||
|
if (document->mimeType() == Constants::C_PY_MIMETYPE) {
|
||||||
|
resetEditorInfoBar(document);
|
||||||
|
LanguageClientManager::reOpenDocumentWithClient(document, client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (FilePath &file : project()->files(Project::AllFiles)) {
|
||||||
|
if (auto document = TextEditor::TextDocument::textDocumentForFilePath(file)) {
|
||||||
|
if (document->mimeType() == Constants::C_PY_MIMETYPE)
|
||||||
|
updateEditorInfoBar(python, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool PythonRunConfiguration::supportsDebugger() const
|
bool PythonRunConfiguration::supportsDebugger() const
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@@ -46,6 +46,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void doAdditionalSetup(const ProjectExplorer::RunConfigurationCreationInfo &) final;
|
void doAdditionalSetup(const ProjectExplorer::RunConfigurationCreationInfo &) final;
|
||||||
|
void updateLanguageServer();
|
||||||
|
|
||||||
bool supportsDebugger() const;
|
bool supportsDebugger() const;
|
||||||
QString mainScript() const;
|
QString mainScript() const;
|
||||||
|
353
src/plugins/python/pythonutils.cpp
Normal file
353
src/plugins/python/pythonutils.cpp
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "pythonutils.h"
|
||||||
|
|
||||||
|
#include "pythonconstants.h"
|
||||||
|
#include "pythonproject.h"
|
||||||
|
#include "pythonrunconfiguration.h"
|
||||||
|
#include "pythonsettings.h"
|
||||||
|
|
||||||
|
#include <coreplugin/infobar.h>
|
||||||
|
#include <coreplugin/progressmanager/progressmanager.h>
|
||||||
|
|
||||||
|
#include <languageclient/languageclientsettings.h>
|
||||||
|
#include <languageclient/languageclientmanager.h>
|
||||||
|
|
||||||
|
#include <projectexplorer/session.h>
|
||||||
|
#include <projectexplorer/target.h>
|
||||||
|
|
||||||
|
#include <texteditor/textdocument.h>
|
||||||
|
|
||||||
|
#include <utils/synchronousprocess.h>
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
using namespace Utils;
|
||||||
|
|
||||||
|
namespace Python {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
static constexpr char startPylsInfoBarId[] = "Python::StartPyls";
|
||||||
|
static constexpr char installPylsInfoBarId[] = "Python::InstallPyls";
|
||||||
|
static constexpr char installPylsTaskId[] = "Python::InstallPylsTask";
|
||||||
|
static constexpr char pythonUtilsTrContext[] = "Python::Utils";
|
||||||
|
|
||||||
|
struct PythonLanguageServerState
|
||||||
|
{
|
||||||
|
enum { CanNotBeInstalled, CanBeInstalled, AlreadyInstalled, AlreadyConfigured } state;
|
||||||
|
FilePath pylsModulePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
static QString pythonName(const FilePath &pythonPath)
|
||||||
|
{
|
||||||
|
static QHash<FilePath, QString> nameForPython;
|
||||||
|
if (!pythonPath.exists())
|
||||||
|
return {};
|
||||||
|
QString name = nameForPython.value(pythonPath);
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
SynchronousProcess pythonProcess;
|
||||||
|
pythonProcess.setTimeoutS(2);
|
||||||
|
const CommandLine pythonVersionCommand(pythonPath, {"--version"});
|
||||||
|
const SynchronousProcessResponse response = pythonProcess.runBlocking(pythonVersionCommand);
|
||||||
|
if (response.result != SynchronousProcessResponse::Finished)
|
||||||
|
return {};
|
||||||
|
name = response.allOutput().trimmed();
|
||||||
|
nameForPython[pythonPath] = name;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePath getPylsModulePath(CommandLine pylsCommand)
|
||||||
|
{
|
||||||
|
pylsCommand.addArg("-h");
|
||||||
|
SynchronousProcess pythonProcess;
|
||||||
|
pythonProcess.setEnvironment(pythonProcess.environment() + QStringList("PYTHONVERBOSE=x"));
|
||||||
|
SynchronousProcessResponse response = pythonProcess.runBlocking(pylsCommand);
|
||||||
|
|
||||||
|
static const QString pylsInitPattern = "(.*)"
|
||||||
|
+ QRegularExpression::escape(
|
||||||
|
QDir::toNativeSeparators("/pyls/__init__.py"))
|
||||||
|
+ '$';
|
||||||
|
static const QRegularExpression regexCached(" matches " + pylsInitPattern,
|
||||||
|
QRegularExpression::MultilineOption);
|
||||||
|
static const QRegularExpression regexNotCached(" code object from " + pylsInitPattern,
|
||||||
|
QRegularExpression::MultilineOption);
|
||||||
|
|
||||||
|
const QString &output = response.allOutput();
|
||||||
|
for (auto regex : {regexCached, regexNotCached}) {
|
||||||
|
QRegularExpressionMatch result = regex.match(output);
|
||||||
|
if (result.hasMatch())
|
||||||
|
return FilePath::fromUserInput(result.captured(1));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<const LanguageClient::StdIOSettings *> configuredPythonLanguageServer()
|
||||||
|
{
|
||||||
|
using namespace LanguageClient;
|
||||||
|
QList<const StdIOSettings *> result;
|
||||||
|
for (const BaseSettings *setting : LanguageClientManager::currentSettings()) {
|
||||||
|
if (setting->m_languageFilter.isSupported(FilePath::fromString("foo.py"),
|
||||||
|
Constants::C_PY_MIMETYPE)) {
|
||||||
|
result << dynamic_cast<const StdIOSettings *>(setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PythonLanguageServerState checkPythonLanguageServer(const FilePath &python)
|
||||||
|
{
|
||||||
|
using namespace LanguageClient;
|
||||||
|
SynchronousProcess pythonProcess;
|
||||||
|
const CommandLine pythonLShelpCommand(python, {"-m", "pyls", "-h"});
|
||||||
|
SynchronousProcessResponse response = pythonProcess.runBlocking(pythonLShelpCommand);
|
||||||
|
if (response.allOutput().contains("Python Language Server")) {
|
||||||
|
const FilePath &modulePath = getPylsModulePath(pythonLShelpCommand);
|
||||||
|
for (const StdIOSettings *serverSetting : configuredPythonLanguageServer()) {
|
||||||
|
if (modulePath == getPylsModulePath(serverSetting->command()))
|
||||||
|
return {PythonLanguageServerState::AlreadyConfigured, FilePath()};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {PythonLanguageServerState::AlreadyInstalled, getPylsModulePath(pythonLShelpCommand)};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandLine pythonPipVersionCommand(python, {"-m", "pip", "-V"});
|
||||||
|
response = pythonProcess.runBlocking(pythonPipVersionCommand);
|
||||||
|
if (response.allOutput().startsWith("pip "))
|
||||||
|
return {PythonLanguageServerState::CanBeInstalled, FilePath()};
|
||||||
|
else
|
||||||
|
return {PythonLanguageServerState::CanNotBeInstalled, FilePath()};
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePath detectPython(const FilePath &documentPath)
|
||||||
|
{
|
||||||
|
FilePath python;
|
||||||
|
|
||||||
|
PythonProject *project = qobject_cast<PythonProject *>(
|
||||||
|
ProjectExplorer::SessionManager::projectForFile(documentPath));
|
||||||
|
if (!project)
|
||||||
|
project = qobject_cast<PythonProject *>(ProjectExplorer::SessionManager::startupProject());
|
||||||
|
|
||||||
|
if (project) {
|
||||||
|
if (auto target = project->activeTarget()) {
|
||||||
|
if (auto runConfig = qobject_cast<PythonRunConfiguration *>(
|
||||||
|
target->activeRunConfiguration())) {
|
||||||
|
python = FilePath::fromString(runConfig->interpreter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!python.exists())
|
||||||
|
python = PythonSettings::defaultInterpreter().command;
|
||||||
|
|
||||||
|
if (!python.exists() && !PythonSettings::interpreters().isEmpty())
|
||||||
|
python = PythonSettings::interpreters().first().command;
|
||||||
|
|
||||||
|
return python;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LanguageClient::StdIOSettings *languageServerForPython(const FilePath &python)
|
||||||
|
{
|
||||||
|
return findOrDefault(configuredPythonLanguageServer(),
|
||||||
|
[pythonModulePath = getPylsModulePath(CommandLine(python, {"-m", "pyls"}))](
|
||||||
|
const LanguageClient::StdIOSettings *setting) {
|
||||||
|
return getPylsModulePath(setting->command()) == pythonModulePath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static LanguageClient::Client *registerLanguageServer(const FilePath &python)
|
||||||
|
{
|
||||||
|
auto *settings = new LanguageClient::StdIOSettings();
|
||||||
|
settings->m_executable = python.toString();
|
||||||
|
settings->m_arguments = "-m pyls";
|
||||||
|
settings->m_name = QCoreApplication::translate(pythonUtilsTrContext,
|
||||||
|
"Python Language Server (%1)")
|
||||||
|
.arg(pythonName(python));
|
||||||
|
settings->m_languageFilter.mimeTypes = QStringList(Constants::C_PY_MIMETYPE);
|
||||||
|
LanguageClient::LanguageClientManager::registerClientSettings(settings);
|
||||||
|
return LanguageClient::LanguageClientManager::clientForSetting(settings).value(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PythonLSInstallHelper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
PythonLSInstallHelper(const FilePath &python, QPointer<TextEditor::TextDocument> document)
|
||||||
|
: m_python(python)
|
||||||
|
, m_document(document)
|
||||||
|
{
|
||||||
|
m_watcher.setFuture(m_future.future());
|
||||||
|
}
|
||||||
|
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
Core::ProgressManager::addTask(m_future.future(), "Install PyLS", installPylsTaskId);
|
||||||
|
connect(&m_process,
|
||||||
|
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||||
|
this,
|
||||||
|
&PythonLSInstallHelper::installFinished);
|
||||||
|
connect(&m_process,
|
||||||
|
&QProcess::readyReadStandardError,
|
||||||
|
this,
|
||||||
|
&PythonLSInstallHelper::errorAvailable);
|
||||||
|
connect(&m_process,
|
||||||
|
&QProcess::readyReadStandardOutput,
|
||||||
|
this,
|
||||||
|
&PythonLSInstallHelper::outputAvailable);
|
||||||
|
|
||||||
|
connect(&m_killTimer, &QTimer::timeout, this, &PythonLSInstallHelper::cancel);
|
||||||
|
connect(&m_watcher, &QFutureWatcher<void>::canceled, this, &PythonLSInstallHelper::cancel);
|
||||||
|
|
||||||
|
// on windows the pyls 0.28.3 crashes with pylint so just install the pyflakes linter
|
||||||
|
const QString &pylsVersion = HostOsInfo::isWindowsHost()
|
||||||
|
? QString{"python-language-server[pyflakes]"}
|
||||||
|
: QString{"python-language-server[all]"};
|
||||||
|
|
||||||
|
m_process.start(m_python.toString(), {"-m", "pip", "install", pylsVersion});
|
||||||
|
|
||||||
|
Core::MessageManager::write(tr("Running '%1 %2' to install python language server")
|
||||||
|
.arg(m_process.program(), m_process.arguments().join(' ')));
|
||||||
|
|
||||||
|
m_killTimer.setSingleShot(true);
|
||||||
|
m_killTimer.start(5 /*minutes*/ * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void cancel()
|
||||||
|
{
|
||||||
|
SynchronousProcess::stopProcess(m_process);
|
||||||
|
Core::MessageManager::write(
|
||||||
|
tr("The Python language server installation canceled by %1.")
|
||||||
|
.arg(m_killTimer.isActive() ? tr("user") : tr("time out")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void installFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
||||||
|
{
|
||||||
|
m_future.reportFinished();
|
||||||
|
if (exitStatus == QProcess::NormalExit && exitCode == 0) {
|
||||||
|
if (LanguageClient::Client *client = registerLanguageServer(m_python))
|
||||||
|
LanguageClient::LanguageClientManager::reOpenDocumentWithClient(m_document, client);
|
||||||
|
} else {
|
||||||
|
Core::MessageManager::write(
|
||||||
|
tr("Installing the Python language server failed with exit code %1").arg(exitCode));
|
||||||
|
}
|
||||||
|
deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void outputAvailable()
|
||||||
|
{
|
||||||
|
const QString &stdOut = QString::fromLocal8Bit(m_process.readAllStandardOutput().trimmed());
|
||||||
|
if (!stdOut.isEmpty())
|
||||||
|
Core::MessageManager::write(stdOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void errorAvailable()
|
||||||
|
{
|
||||||
|
const QString &stdErr = QString::fromLocal8Bit(m_process.readAllStandardError().trimmed());
|
||||||
|
if (!stdErr.isEmpty())
|
||||||
|
Core::MessageManager::write(stdErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
QFutureInterface<void> m_future;
|
||||||
|
QFutureWatcher<void> m_watcher;
|
||||||
|
QProcess m_process;
|
||||||
|
QTimer m_killTimer;
|
||||||
|
const FilePath m_python;
|
||||||
|
QPointer<TextEditor::TextDocument> m_document;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void installPythonLanguageServer(const FilePath &python,
|
||||||
|
QPointer<TextEditor::TextDocument> document)
|
||||||
|
{
|
||||||
|
document->infoBar()->removeInfo(installPylsInfoBarId);
|
||||||
|
|
||||||
|
auto install = new PythonLSInstallHelper(python, document);
|
||||||
|
install->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setupPythonLanguageServer(const FilePath &python,
|
||||||
|
QPointer<TextEditor::TextDocument> document)
|
||||||
|
{
|
||||||
|
document->infoBar()->removeInfo(startPylsInfoBarId);
|
||||||
|
if (LanguageClient::Client *client = registerLanguageServer(python))
|
||||||
|
LanguageClient::LanguageClientManager::reOpenDocumentWithClient(document, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateEditorInfoBar(const FilePath &python, TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
const PythonLanguageServerState &lsState = checkPythonLanguageServer(python);
|
||||||
|
|
||||||
|
if (lsState.state == PythonLanguageServerState::CanNotBeInstalled
|
||||||
|
|| lsState.state == PythonLanguageServerState::AlreadyConfigured) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::InfoBar *infoBar = document->infoBar();
|
||||||
|
infoBar->removeInfo(installPylsInfoBarId);
|
||||||
|
infoBar->removeInfo(startPylsInfoBarId);
|
||||||
|
if (lsState.state == PythonLanguageServerState::CanBeInstalled
|
||||||
|
&& infoBar->canInfoBeAdded(installPylsInfoBarId)) {
|
||||||
|
auto message
|
||||||
|
= QCoreApplication::translate(pythonUtilsTrContext,
|
||||||
|
"Install and set up Python language server (PyLS) for %1 (%2). "
|
||||||
|
"The language server provides Python specific completions and annotations.")
|
||||||
|
.arg(pythonName(python), python.toUserOutput());
|
||||||
|
Core::InfoBarEntry info(installPylsInfoBarId,
|
||||||
|
message,
|
||||||
|
Core::InfoBarEntry::GlobalSuppression::Enabled);
|
||||||
|
info.setCustomButtonInfo(QCoreApplication::translate(pythonUtilsTrContext, "Install"),
|
||||||
|
[=]() { installPythonLanguageServer(python, document); });
|
||||||
|
infoBar->addInfo(info);
|
||||||
|
} else if (lsState.state == PythonLanguageServerState::AlreadyInstalled
|
||||||
|
&& infoBar->canInfoBeAdded(startPylsInfoBarId)) {
|
||||||
|
auto message = QCoreApplication::translate(pythonUtilsTrContext,
|
||||||
|
"Found a Python language server for %1 (%2). "
|
||||||
|
"Should this one be set up for this document?")
|
||||||
|
.arg(pythonName(python), python.toUserOutput());
|
||||||
|
Core::InfoBarEntry info(startPylsInfoBarId,
|
||||||
|
message,
|
||||||
|
Core::InfoBarEntry::GlobalSuppression::Enabled);
|
||||||
|
info.setCustomButtonInfo(QCoreApplication::translate(pythonUtilsTrContext, "Setup"),
|
||||||
|
[=]() { setupPythonLanguageServer(python, document); });
|
||||||
|
infoBar->addInfo(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetEditorInfoBar(TextEditor::TextDocument *document)
|
||||||
|
{
|
||||||
|
Core::InfoBar *infoBar = document->infoBar();
|
||||||
|
infoBar->removeInfo(installPylsInfoBarId);
|
||||||
|
infoBar->removeInfo(startPylsInfoBarId);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace Python
|
||||||
|
|
||||||
|
#include "pythonutils.moc"
|
||||||
|
|
43
src/plugins/python/pythonutils.h
Normal file
43
src/plugins/python/pythonutils.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <utils/fileutils.h>
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace TextEditor { class TextDocument; }
|
||||||
|
namespace LanguageClient { class StdIOSettings; }
|
||||||
|
|
||||||
|
namespace Python {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
QList<const LanguageClient::StdIOSettings *> configuredPythonLanguageServers();
|
||||||
|
const LanguageClient::StdIOSettings *languageServerForPython(const Utils::FilePath &python);
|
||||||
|
Utils::FilePath detectPython(const Utils::FilePath &NdocumentPath);
|
||||||
|
void updateEditorInfoBar(const Utils::FilePath &python, TextEditor::TextDocument *document);
|
||||||
|
void resetEditorInfoBar(TextEditor::TextDocument *document);
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace Python
|
Reference in New Issue
Block a user