From c8ccfea225d506513e5575dd100bf6175720e29a Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 1 Oct 2019 13:16:17 +0200 Subject: Python: Switch pyls on interpreter change Change-Id: I458b635986a55003a1e7254e27e2df9667704273 Reviewed-by: Christian Stenger --- src/plugins/python/pythoneditor.cpp | 315 +----------------------------------- 1 file changed, 4 insertions(+), 311 deletions(-) (limited to 'src/plugins/python/pythoneditor.cpp') diff --git a/src/plugins/python/pythoneditor.cpp b/src/plugins/python/pythoneditor.cpp index 8422eb221f..6481897e69 100644 --- a/src/plugins/python/pythoneditor.cpp +++ b/src/plugins/python/pythoneditor.cpp @@ -27,327 +27,22 @@ #include "pythonconstants.h" #include "pythonhighlighter.h" #include "pythonindenter.h" -#include "pythonplugin.h" -#include "pythonproject.h" -#include "pythonrunconfiguration.h" -#include "pythonsettings.h" - -#include -#include - -#include -#include -#include - -#include -#include +#include "pythonutils.h" #include #include -#include - -#include -#include -#include - -#include -#include -#include -#include - -using namespace ProjectExplorer; -using namespace Utils; namespace Python { 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 cachedName; -}; - -static PythonForProject detectPython(TextEditor::TextDocument *document) -{ - PythonForProject python; - - python.project = qobject_cast( - SessionManager::projectForFile(document->filePath())); - if (!python.project) - python.project = qobject_cast(SessionManager::startupProject()); - - if (python.project) { - if (auto target = python.project->activeTarget()) { - if (auto runConfig = qobject_cast( - 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 configuredPythonLanguageServer( - Core::IDocument *doc) -{ - using namespace LanguageClient; - QList result; - for (const BaseSettings *setting : LanguageClientManager::currentSettings()) { - if (setting->m_languageFilter.isSupported(doc)) - result << dynamic_cast(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 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::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::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 m_future; - QFutureWatcher m_watcher; - QProcess m_process; - QTimer m_killTimer; - const PythonForProject m_python; - QPointer m_document; -}; - -static void installPythonLanguageServer(const PythonForProject &python, - QPointer document) -{ - document->infoBar()->removeInfo(installPylsInfoBarId); - - auto install = new PythonLSInstallHelper(python, document); - install->run(); -} - -static void setupPythonLanguageServer(const PythonForProject &python, - QPointer 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) { auto textDocument = qobject_cast(document); if (!textDocument || textDocument->mimeType() != Constants::C_PY_MIMETYPE) return; - const PythonForProject &python = detectPython(textDocument); - if (!python.path.exists()) + const Utils::FilePath &python = detectPython(textDocument->filePath()); + if (!python.exists()) return; updateEditorInfoBar(python, textDocument); @@ -368,7 +63,7 @@ PythonEditorFactory::PythonEditorFactory() setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_PYTHONEDITOR_ID); }); setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); }); setSyntaxHighlighterCreator([] { return new PythonHighlighter; }); - setCommentDefinition(CommentDefinition::HashStyle); + setCommentDefinition(Utils::CommentDefinition::HashStyle); setParenthesesMatchingEnabled(true); setCodeFoldingSupported(true); @@ -378,5 +73,3 @@ PythonEditorFactory::PythonEditorFactory() } // namespace Internal } // namespace Python - -#include "pythoneditor.moc" -- cgit v1.2.1