diff options
author | David Schulz <david.schulz@qt.io> | 2024-04-24 13:16:56 +0200 |
---|---|---|
committer | David Schulz <david.schulz@qt.io> | 2024-05-03 06:14:18 +0000 |
commit | 546f25c3dea72c8218a1ed1bbc808891e0982cdf (patch) | |
tree | 0befe24f647e6240588727216531d2c73aaaaf67 /src/plugins/python | |
parent | 8fa9a25d08ea06a5a7f6fe14821e0049cef95516 (diff) |
Python: offer to install python-lsp-server updates
Change-Id: I4068da0783b0a8e2becfcd04d480c6ad2e2f5b7c
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Diffstat (limited to 'src/plugins/python')
-rw-r--r-- | src/plugins/python/pipsupport.cpp | 30 | ||||
-rw-r--r-- | src/plugins/python/pipsupport.h | 4 | ||||
-rw-r--r-- | src/plugins/python/pythonlanguageclient.cpp | 106 |
3 files changed, 112 insertions, 28 deletions
diff --git a/src/plugins/python/pipsupport.cpp b/src/plugins/python/pipsupport.cpp index 8723ca75109..673dbdccf33 100644 --- a/src/plugins/python/pipsupport.cpp +++ b/src/plugins/python/pipsupport.cpp @@ -66,18 +66,21 @@ void PipInstallTask::run() emit finished(false); return; } - const QString taskTitle = Tr::tr("Install Python Packages"); - Core::ProgressManager::addTask(m_future.future(), taskTitle, pipInstallTaskId); + QString operation = Tr::tr("Install"); + QString operant; QStringList arguments = {"-m", "pip", "install"}; - if (!m_requirementsFile.isEmpty()) + if (!m_requirementsFile.isEmpty()) { + operant = Tr::tr("Requirements"); arguments << "-r" << m_requirementsFile.toString(); - else { + } else { + for (const PipPackage &package : m_packages) { QString pipPackage = package.packageName; if (!package.version.isEmpty()) pipPackage += "==" + package.version; arguments << pipPackage; } + operant = m_packages.count() == 1 ? m_packages.first().displayName : Tr::tr("Packages"); } if (!m_targetPath.isEmpty()) { @@ -87,10 +90,17 @@ void PipInstallTask::run() arguments << "--user"; // add --user to global pythons, but skip it for venv pythons } + if (m_upgrade) { + arguments << "--upgrade"; + operation = Tr::tr("Update"); + } + m_process.setCommand({m_python, arguments}); - m_process.setTerminalMode(TerminalMode::Run); + m_process.setTerminalMode(m_silent ? TerminalMode::Off : TerminalMode::Run); m_process.start(); + const QString taskTitle = Tr::tr("%1 %2").arg(operation).arg(operant); + Core::ProgressManager::addTask(m_future.future(), taskTitle, pipInstallTaskId); Core::MessageManager::writeSilently( Tr::tr("Running \"%1\" to install %2.") .arg(m_process.commandLine().toUserOutput(), packagesDisplayName())); @@ -143,6 +153,16 @@ QString PipInstallTask::packagesDisplayName() const : m_requirementsFile.toUserOutput(); } +void PipInstallTask::setUpgrade(bool upgrade) +{ + m_upgrade = upgrade; +} + +void PipInstallTask::setSilent(bool silent) +{ + m_silent = silent; +} + void PipPackageInfo::parseField(const QString &field, const QStringList &data) { if (field.isEmpty()) diff --git a/src/plugins/python/pipsupport.h b/src/plugins/python/pipsupport.h index da217282eb9..35e6e765aac 100644 --- a/src/plugins/python/pipsupport.h +++ b/src/plugins/python/pipsupport.h @@ -68,6 +68,8 @@ public: void addPackage(const PipPackage &package); void setPackages(const QList<PipPackage> &packages); void setTargetPath(const Utils::FilePath &targetPath); + void setUpgrade(bool upgrade); + void setSilent(bool silent); void run(); signals: @@ -86,6 +88,8 @@ private: Utils::FilePath m_requirementsFile; Utils::FilePath m_targetPath; Utils::Process m_process; + bool m_upgrade = false; + bool m_silent = false; QFutureInterface<void> m_future; QFutureWatcher<void> m_watcher; QTimer m_killTimer; diff --git a/src/plugins/python/pythonlanguageclient.cpp b/src/plugins/python/pythonlanguageclient.cpp index 86cebde0e94..2379ffb1a19 100644 --- a/src/plugins/python/pythonlanguageclient.cpp +++ b/src/plugins/python/pythonlanguageclient.cpp @@ -46,14 +46,17 @@ using namespace Utils; namespace Python::Internal { static constexpr char installPylsInfoBarId[] = "Python::InstallPyls"; +static constexpr char updatePylsInfoBarId[] = "Python::updatePyls"; +static constexpr char alwaysUpdateKey[] = "Python/AlwaysUpdatePyls"; class PythonLanguageServerState { public: enum { - CanNotBeInstalled, - CanBeInstalled, - AlreadyInstalled + NotInstallable, + Installable, + Updatable, + Installed } state; FilePath pylsModulePath; }; @@ -79,18 +82,40 @@ static PythonLanguageServerState checkPythonLanguageServer(const FilePath &pytho using namespace LanguageClient; auto lspPath = pyLspPath(python); if (lspPath.isEmpty()) - return {PythonLanguageServerState::CanNotBeInstalled, FilePath()}; - - if (lspPath.pathAppended("bin").pathAppended("pylsp").withExecutableSuffix().exists()) - return {PythonLanguageServerState::AlreadyInstalled, lspPath}; + return {PythonLanguageServerState::NotInstallable, FilePath()}; Process pythonProcess; pythonProcess.setCommand({python, {"-m", "pip", "-V"}}); using namespace std::chrono_literals; pythonProcess.runBlocking(2s); - if (pythonProcess.allOutput().startsWith("pip ")) - return {PythonLanguageServerState::CanBeInstalled, lspPath}; - return {PythonLanguageServerState::CanNotBeInstalled, FilePath()}; + bool pipAvailable = pythonProcess.allOutput().startsWith("pip "); + + if (lspPath.pathAppended("bin").pathAppended("pylsp").withExecutableSuffix().exists()) { + if (pipAvailable) { + Process pythonProcess; + Environment env = pythonProcess.environment(); + env.set("PYTHONPATH", lspPath.toUserOutput()); + pythonProcess.setEnvironment(env); + pythonProcess.setCommand({python, {"-m", "pip", "list", "--outdated", "--format=json"}}); + pythonProcess.runBlocking(20s); + QString output = pythonProcess.allOutput(); + + // Only the first line contains the json data. Following lines might contain warnings. + if (int index = output.indexOf('\n'); index >= 0) + output.truncate(index); + + const QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8()); + for (const QJsonValue &value : doc.array()) { + if (value.toObject().value("name") == "python-lsp-server") + return {PythonLanguageServerState::Updatable, lspPath}; + } + } + return {PythonLanguageServerState::Installed, lspPath}; + } + + if (pipAvailable) + return {PythonLanguageServerState::Installable, lspPath}; + return {PythonLanguageServerState::NotInstallable, FilePath()}; } @@ -256,23 +281,25 @@ class PyLSConfigureAssistant : public QObject public: PyLSConfigureAssistant(); - void handlePyLSState(const Utils::FilePath &python, + void handlePyLSState(const FilePath &python, const PythonLanguageServerState &state, TextEditor::TextDocument *document); void resetEditorInfoBar(TextEditor::TextDocument *document); - void installPythonLanguageServer(const Utils::FilePath &python, + void installPythonLanguageServer(const FilePath &python, QPointer<TextEditor::TextDocument> document, - const Utils::FilePath &pylsPath); + const FilePath &pylsPath, bool silent, bool upgrade); void openDocument(const FilePath &python, TextEditor::TextDocument *document); - QHash<Utils::FilePath, QList<TextEditor::TextDocument *>> m_infoBarEntries; + QHash<FilePath, QList<TextEditor::TextDocument *>> m_infoBarEntries; QHash<TextEditor::TextDocument *, QPointer<QFutureWatcher<PythonLanguageServerState>>> m_runningChecks; }; void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python, QPointer<TextEditor::TextDocument> document, - const FilePath &pylsPath) + const FilePath &pylsPath, + bool silent, + bool upgrade) { document->infoBar()->removeInfo(installPylsInfoBarId); @@ -299,6 +326,8 @@ void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python, install->setTargetPath(pylsPath); install->setPackages({PipPackage{"python-lsp-server[all]", "Python Language Server"}}); + install->setUpgrade(upgrade); + install->setSilent(silent); install->run(); } @@ -342,24 +371,55 @@ void PyLSConfigureAssistant::handlePyLSState(const FilePath &python, const PythonLanguageServerState &state, TextEditor::TextDocument *document) { - if (state.state == PythonLanguageServerState::CanNotBeInstalled) + if (state.state == PythonLanguageServerState::NotInstallable) return; - Utils::InfoBar *infoBar = document->infoBar(); - if (state.state == PythonLanguageServerState::CanBeInstalled + InfoBar *infoBar = document->infoBar(); + if (state.state == PythonLanguageServerState::Installable && infoBar->canInfoBeAdded(installPylsInfoBarId)) { auto message = Tr::tr("Install Python language server (PyLS) for %1 (%2). " "The language server provides Python specific completion and annotation.") .arg(pythonName(python), python.toUserOutput()); - Utils::InfoBarEntry info(installPylsInfoBarId, - message, - Utils::InfoBarEntry::GlobalSuppression::Enabled); + InfoBarEntry info(installPylsInfoBarId, message, InfoBarEntry::GlobalSuppression::Enabled); info.addCustomButton(Tr::tr("Install"), [this, python, document, state] { - installPythonLanguageServer(python, document, state.pylsModulePath); + installPythonLanguageServer(python, document, state.pylsModulePath, false, false); }); infoBar->addInfo(info); m_infoBarEntries[python] << document; - } else if (state.state == PythonLanguageServerState::AlreadyInstalled) { + } else if (state.state == PythonLanguageServerState::Updatable) { + if (infoBar->canInfoBeAdded(updatePylsInfoBarId)) { + auto message = Tr::tr("Update Python language server (PyLS) for %1 (%2).") + .arg(pythonName(python), python.toUserOutput()); + InfoBarEntry info(updatePylsInfoBarId, message); + info.addCustomButton(Tr::tr("Always Update"), [this, python, document, state] { + document->infoBar()->removeInfo(updatePylsInfoBarId); + Core::ICore::settings()->setValue(alwaysUpdateKey, true); + InfoBar::globallySuppressInfo(updatePylsInfoBarId); + installPythonLanguageServer(python, document, state.pylsModulePath, false, true); + }); + info.addCustomButton(Tr::tr("Update"), [this, python, document, state] { + document->infoBar()->removeInfo(updatePylsInfoBarId); + installPythonLanguageServer(python, document, state.pylsModulePath, false, true); + }); + info.addCustomButton(Tr::tr("Never"), [document, python] { + document->infoBar()->removeInfo(updatePylsInfoBarId); + InfoBar::globallySuppressInfo(updatePylsInfoBarId); + if (auto client = clientForPython(python)) + LanguageClientManager::openDocumentWithClient(document, client); + }); + info.setCancelButtonInfo([python, document]{ + if (auto client = clientForPython(python)) + LanguageClientManager::openDocumentWithClient(document, client); + }); + infoBar->addInfo(info); + + m_infoBarEntries[python] << document; + } else if (Core::ICore::settings()->value(alwaysUpdateKey, false).toBool()) { + installPythonLanguageServer(python, document, state.pylsModulePath, true, true); + } else if (auto client = clientForPython(python)) { + LanguageClientManager::openDocumentWithClient(document, client); + } + } else if (state.state == PythonLanguageServerState::Installed) { if (auto client = clientForPython(python)) LanguageClientManager::openDocumentWithClient(document, client); } |