Python: offer to install python-lsp-server updates

Change-Id: I4068da0783b0a8e2becfcd04d480c6ad2e2f5b7c
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2024-04-24 13:16:56 +02:00
parent 8fa9a25d08
commit 546f25c3de
3 changed files with 112 additions and 28 deletions

View File

@@ -66,18 +66,21 @@ void PipInstallTask::run()
emit finished(false); emit finished(false);
return; return;
} }
const QString taskTitle = Tr::tr("Install Python Packages"); QString operation = Tr::tr("Install");
Core::ProgressManager::addTask(m_future.future(), taskTitle, pipInstallTaskId); QString operant;
QStringList arguments = {"-m", "pip", "install"}; QStringList arguments = {"-m", "pip", "install"};
if (!m_requirementsFile.isEmpty()) if (!m_requirementsFile.isEmpty()) {
operant = Tr::tr("Requirements");
arguments << "-r" << m_requirementsFile.toString(); arguments << "-r" << m_requirementsFile.toString();
else { } else {
for (const PipPackage &package : m_packages) { for (const PipPackage &package : m_packages) {
QString pipPackage = package.packageName; QString pipPackage = package.packageName;
if (!package.version.isEmpty()) if (!package.version.isEmpty())
pipPackage += "==" + package.version; pipPackage += "==" + package.version;
arguments << pipPackage; arguments << pipPackage;
} }
operant = m_packages.count() == 1 ? m_packages.first().displayName : Tr::tr("Packages");
} }
if (!m_targetPath.isEmpty()) { if (!m_targetPath.isEmpty()) {
@@ -87,10 +90,17 @@ void PipInstallTask::run()
arguments << "--user"; // add --user to global pythons, but skip it for venv pythons 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.setCommand({m_python, arguments});
m_process.setTerminalMode(TerminalMode::Run); m_process.setTerminalMode(m_silent ? TerminalMode::Off : TerminalMode::Run);
m_process.start(); 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( Core::MessageManager::writeSilently(
Tr::tr("Running \"%1\" to install %2.") Tr::tr("Running \"%1\" to install %2.")
.arg(m_process.commandLine().toUserOutput(), packagesDisplayName())); .arg(m_process.commandLine().toUserOutput(), packagesDisplayName()));
@@ -143,6 +153,16 @@ QString PipInstallTask::packagesDisplayName() const
: m_requirementsFile.toUserOutput(); : 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) void PipPackageInfo::parseField(const QString &field, const QStringList &data)
{ {
if (field.isEmpty()) if (field.isEmpty())

View File

@@ -68,6 +68,8 @@ public:
void addPackage(const PipPackage &package); void addPackage(const PipPackage &package);
void setPackages(const QList<PipPackage> &packages); void setPackages(const QList<PipPackage> &packages);
void setTargetPath(const Utils::FilePath &targetPath); void setTargetPath(const Utils::FilePath &targetPath);
void setUpgrade(bool upgrade);
void setSilent(bool silent);
void run(); void run();
signals: signals:
@@ -86,6 +88,8 @@ private:
Utils::FilePath m_requirementsFile; Utils::FilePath m_requirementsFile;
Utils::FilePath m_targetPath; Utils::FilePath m_targetPath;
Utils::Process m_process; Utils::Process m_process;
bool m_upgrade = false;
bool m_silent = false;
QFutureInterface<void> m_future; QFutureInterface<void> m_future;
QFutureWatcher<void> m_watcher; QFutureWatcher<void> m_watcher;
QTimer m_killTimer; QTimer m_killTimer;

View File

@@ -46,14 +46,17 @@ using namespace Utils;
namespace Python::Internal { namespace Python::Internal {
static constexpr char installPylsInfoBarId[] = "Python::InstallPyls"; static constexpr char installPylsInfoBarId[] = "Python::InstallPyls";
static constexpr char updatePylsInfoBarId[] = "Python::updatePyls";
static constexpr char alwaysUpdateKey[] = "Python/AlwaysUpdatePyls";
class PythonLanguageServerState class PythonLanguageServerState
{ {
public: public:
enum { enum {
CanNotBeInstalled, NotInstallable,
CanBeInstalled, Installable,
AlreadyInstalled Updatable,
Installed
} state; } state;
FilePath pylsModulePath; FilePath pylsModulePath;
}; };
@@ -79,18 +82,40 @@ static PythonLanguageServerState checkPythonLanguageServer(const FilePath &pytho
using namespace LanguageClient; using namespace LanguageClient;
auto lspPath = pyLspPath(python); auto lspPath = pyLspPath(python);
if (lspPath.isEmpty()) if (lspPath.isEmpty())
return {PythonLanguageServerState::CanNotBeInstalled, FilePath()}; return {PythonLanguageServerState::NotInstallable, FilePath()};
if (lspPath.pathAppended("bin").pathAppended("pylsp").withExecutableSuffix().exists())
return {PythonLanguageServerState::AlreadyInstalled, lspPath};
Process pythonProcess; Process pythonProcess;
pythonProcess.setCommand({python, {"-m", "pip", "-V"}}); pythonProcess.setCommand({python, {"-m", "pip", "-V"}});
using namespace std::chrono_literals; using namespace std::chrono_literals;
pythonProcess.runBlocking(2s); pythonProcess.runBlocking(2s);
if (pythonProcess.allOutput().startsWith("pip ")) bool pipAvailable = pythonProcess.allOutput().startsWith("pip ");
return {PythonLanguageServerState::CanBeInstalled, lspPath};
return {PythonLanguageServerState::CanNotBeInstalled, FilePath()}; 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: public:
PyLSConfigureAssistant(); PyLSConfigureAssistant();
void handlePyLSState(const Utils::FilePath &python, void handlePyLSState(const FilePath &python,
const PythonLanguageServerState &state, const PythonLanguageServerState &state,
TextEditor::TextDocument *document); TextEditor::TextDocument *document);
void resetEditorInfoBar(TextEditor::TextDocument *document); void resetEditorInfoBar(TextEditor::TextDocument *document);
void installPythonLanguageServer(const Utils::FilePath &python, void installPythonLanguageServer(const FilePath &python,
QPointer<TextEditor::TextDocument> document, QPointer<TextEditor::TextDocument> document,
const Utils::FilePath &pylsPath); const FilePath &pylsPath, bool silent, bool upgrade);
void openDocument(const FilePath &python, TextEditor::TextDocument *document); 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>>> QHash<TextEditor::TextDocument *, QPointer<QFutureWatcher<PythonLanguageServerState>>>
m_runningChecks; m_runningChecks;
}; };
void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python, void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python,
QPointer<TextEditor::TextDocument> document, QPointer<TextEditor::TextDocument> document,
const FilePath &pylsPath) const FilePath &pylsPath,
bool silent,
bool upgrade)
{ {
document->infoBar()->removeInfo(installPylsInfoBarId); document->infoBar()->removeInfo(installPylsInfoBarId);
@@ -299,6 +326,8 @@ void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python,
install->setTargetPath(pylsPath); install->setTargetPath(pylsPath);
install->setPackages({PipPackage{"python-lsp-server[all]", "Python Language Server"}}); install->setPackages({PipPackage{"python-lsp-server[all]", "Python Language Server"}});
install->setUpgrade(upgrade);
install->setSilent(silent);
install->run(); install->run();
} }
@@ -342,24 +371,55 @@ void PyLSConfigureAssistant::handlePyLSState(const FilePath &python,
const PythonLanguageServerState &state, const PythonLanguageServerState &state,
TextEditor::TextDocument *document) TextEditor::TextDocument *document)
{ {
if (state.state == PythonLanguageServerState::CanNotBeInstalled) if (state.state == PythonLanguageServerState::NotInstallable)
return; return;
Utils::InfoBar *infoBar = document->infoBar(); InfoBar *infoBar = document->infoBar();
if (state.state == PythonLanguageServerState::CanBeInstalled if (state.state == PythonLanguageServerState::Installable
&& infoBar->canInfoBeAdded(installPylsInfoBarId)) { && infoBar->canInfoBeAdded(installPylsInfoBarId)) {
auto message = Tr::tr("Install Python language server (PyLS) for %1 (%2). " auto message = Tr::tr("Install Python language server (PyLS) for %1 (%2). "
"The language server provides Python specific completion and annotation.") "The language server provides Python specific completion and annotation.")
.arg(pythonName(python), python.toUserOutput()); .arg(pythonName(python), python.toUserOutput());
Utils::InfoBarEntry info(installPylsInfoBarId, InfoBarEntry info(installPylsInfoBarId, message, InfoBarEntry::GlobalSuppression::Enabled);
message,
Utils::InfoBarEntry::GlobalSuppression::Enabled);
info.addCustomButton(Tr::tr("Install"), [this, python, document, state] { info.addCustomButton(Tr::tr("Install"), [this, python, document, state] {
installPythonLanguageServer(python, document, state.pylsModulePath); installPythonLanguageServer(python, document, state.pylsModulePath, false, false);
}); });
infoBar->addInfo(info); infoBar->addInfo(info);
m_infoBarEntries[python] << document; 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)) if (auto client = clientForPython(python))
LanguageClientManager::openDocumentWithClient(document, client); LanguageClientManager::openDocumentWithClient(document, client);
} }