forked from qt-creator/qt-creator
Python: offer to install python-lsp-server updates
Change-Id: I4068da0783b0a8e2becfcd04d480c6ad2e2f5b7c Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
@@ -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())
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user