From 90963249a40536783f7f1b82fc714b6b0ca70b01 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Mon, 23 Sep 2019 12:10:09 +0200 Subject: [PATCH] Python: Add info bar to install python language server If the detected python for the current document does not have an installed language server, check whether pip is installed, try to install the language server and if that was successful set it up. Change-Id: Ib6cf3bacdcf3489728990cace5417862b7c78be5 Reviewed-by: Christian Stenger --- src/plugins/python/pythoneditor.cpp | 107 +++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/src/plugins/python/pythoneditor.cpp b/src/plugins/python/pythoneditor.cpp index 434ec9e1cc1..8b0c3b84542 100644 --- a/src/plugins/python/pythoneditor.cpp +++ b/src/plugins/python/pythoneditor.cpp @@ -45,11 +45,13 @@ #include #include +#include #include #include #include #include +#include using namespace ProjectExplorer; using namespace Utils; @@ -58,6 +60,7 @@ namespace Python { namespace Internal { static constexpr char startPylsInfoBarId[] = "PythonEditor::StartPyls"; +static constexpr char installPylsInfoBarId[] = "PythonEditor::InstallPyls"; struct PythonForProject { @@ -193,6 +196,90 @@ static LanguageClient::Client *registerLanguageServer(const PythonForProject &py 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) + {} + + void run() + { + auto killTimer = new QTimer(&m_process); + + 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(killTimer, &QTimer::timeout, [this]() { + SynchronousProcess::stopProcess(m_process); + Core::MessageManager::write(tr("The Python language server installation timed out.")); + }); + + // 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(' '))); + + killTimer->start(5 /*minutes*/ * 60 * 1000); + } + +private: + void installFinished(int exitCode, QProcess::ExitStatus exitStatus) + { + 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); + } + + QProcess m_process; + 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) { @@ -206,13 +293,25 @@ static void updateEditorInfoBar(const PythonForProject &python, TextEditor::Text const PythonLanguageServerState &lsState = checkPythonLanguageServer(python.path, document); if (lsState.state == PythonLanguageServerState::CanNotBeInstalled - || lsState.state == PythonLanguageServerState::AlreadyConfigured - || lsState.state == PythonLanguageServerState::CanBeInstalled /* TODO */) { + || lsState.state == PythonLanguageServerState::AlreadyConfigured) { return; } Core::InfoBar *infoBar = document->infoBar(); - if (lsState.state == PythonLanguageServerState::AlreadyInstalled + if (lsState.state == PythonLanguageServerState::CanBeInstalled + && infoBar->canInfoBeAdded(installPylsInfoBarId)) { + auto message + = PythonEditorFactory::tr( + "Install and set up Python language server 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?") @@ -264,3 +363,5 @@ PythonEditorFactory::PythonEditorFactory() } // namespace Internal } // namespace Python + +#include "pythoneditor.moc"