aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/python/pythonutils.cpp
diff options
context:
space:
mode:
authorDavid Schulz <david.schulz@qt.io>2021-12-13 14:19:30 +0100
committerDavid Schulz <david.schulz@qt.io>2022-01-18 08:14:57 +0000
commit49ac087955789e4ab931afe3a34414c7cba68589 (patch)
treed29c504a53c847130b65e49bae89cd1070ef90ab /src/plugins/python/pythonutils.cpp
parent1ba6faeea01cd5eeba854d559344eb01a5c64574 (diff)
Python: move language client functionality out of utils
There will be more lsp specific functionality so moving it into its own space is reasonable. Change-Id: Ic87d437182d68673b53f662c804707138fef5b6c Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Diffstat (limited to 'src/plugins/python/pythonutils.cpp')
-rw-r--r--src/plugins/python/pythonutils.cpp425
1 files changed, 4 insertions, 421 deletions
diff --git a/src/plugins/python/pythonutils.cpp b/src/plugins/python/pythonutils.cpp
index a98fdc449a3..74294263a93 100644
--- a/src/plugins/python/pythonutils.cpp
+++ b/src/plugins/python/pythonutils.cpp
@@ -25,155 +25,26 @@
#include "pythonutils.h"
-#include "pythonconstants.h"
-#include "pythonplugin.h"
#include "pythonproject.h"
#include "pythonrunconfiguration.h"
#include "pythonsettings.h"
-#include <coreplugin/editormanager/editormanager.h>
-#include <coreplugin/progressmanager/progressmanager.h>
-
-#include <languageclient/languageclientmanager.h>
-#include <languageclient/languageclientsettings.h>
+#include <coreplugin/messagemanager.h>
+#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
-#include <texteditor/textdocument.h>
-
+#include <utils/algorithm.h>
#include <utils/consoleprocess.h>
-#include <utils/infobar.h>
#include <utils/mimetypes/mimedatabase.h>
-#include <utils/qtcassert.h>
-#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
-#include <QDir>
-#include <QFutureWatcher>
-#include <QRegularExpression>
-#include <QTimer>
-
-using namespace LanguageClient;
using namespace Utils;
namespace Python {
namespace Internal {
-static constexpr char startPylsInfoBarId[] = "Python::StartPyls";
-static constexpr char installPylsInfoBarId[] = "Python::InstallPyls";
-static constexpr char enablePylsInfoBarId[] = "Python::EnablePyls";
-static constexpr char installPylsTaskId[] = "Python::InstallPylsTask";
-
-struct PythonLanguageServerState
-{
- enum {
- CanNotBeInstalled,
- CanBeInstalled,
- AlreadyInstalled,
- AlreadyConfigured,
- ConfiguredButDisabled
- } state;
- FilePath pylsModulePath;
-};
-
-static QString pythonName(const FilePath &pythonPath)
-{
- static QHash<FilePath, QString> nameForPython;
- if (!pythonPath.exists())
- return {};
- QString name = nameForPython.value(pythonPath);
- if (name.isEmpty()) {
- QtcProcess pythonProcess;
- pythonProcess.setTimeoutS(2);
- pythonProcess.setCommand({pythonPath, {"--version"}});
- pythonProcess.runBlocking();
- if (pythonProcess.result() != QtcProcess::FinishedWithSuccess)
- return {};
- name = pythonProcess.allOutput().trimmed();
- nameForPython[pythonPath] = name;
- }
- return name;
-}
-
-FilePath getPylsModulePath(CommandLine pylsCommand)
-{
- static QMutex mutex; // protect the access to the cache
- QMutexLocker locker(&mutex);
- static QMap<FilePath, FilePath> cache;
- const FilePath &modulePath = cache.value(pylsCommand.executable());
- if (!modulePath.isEmpty())
- return modulePath;
-
- pylsCommand.addArg("-h");
-
- QtcProcess pythonProcess;
- Environment env = pythonProcess.environment();
- env.set("PYTHONVERBOSE", "x");
- pythonProcess.setEnvironment(env);
- pythonProcess.setCommand(pylsCommand);
- pythonProcess.runBlocking();
-
- static const QString pylsInitPattern = "(.*)"
- + QRegularExpression::escape(
- QDir::toNativeSeparators("/pylsp/__init__.py"))
- + '$';
- static const QRegularExpression regexCached(" matches " + pylsInitPattern,
- QRegularExpression::MultilineOption);
- static const QRegularExpression regexNotCached(" code object from " + pylsInitPattern,
- QRegularExpression::MultilineOption);
-
- const QString output = pythonProcess.allOutput();
- for (const auto &regex : {regexCached, regexNotCached}) {
- const QRegularExpressionMatch result = regex.match(output);
- if (result.hasMatch()) {
- const FilePath &modulePath = FilePath::fromUserInput(result.captured(1));
- cache[pylsCommand.executable()] = modulePath;
- return modulePath;
- }
- }
- return {};
-}
-
-QList<const StdIOSettings *> configuredPythonLanguageServer()
-{
- using namespace LanguageClient;
- QList<const StdIOSettings *> result;
- for (const BaseSettings *setting : LanguageClientManager::currentSettings()) {
- if (setting->m_languageFilter.isSupported("foo.py", Constants::C_PY_MIMETYPE))
- result << dynamic_cast<const StdIOSettings *>(setting);
- }
- return result;
-}
-
-static PythonLanguageServerState checkPythonLanguageServer(const FilePath &python)
-{
- using namespace LanguageClient;
- const CommandLine pythonLShelpCommand(python, {"-m", "pylsp", "-h"});
- const FilePath &modulePath = getPylsModulePath(pythonLShelpCommand);
- for (const StdIOSettings *serverSetting : configuredPythonLanguageServer()) {
- if (modulePath == getPylsModulePath(serverSetting->command())) {
- return {serverSetting->m_enabled ? PythonLanguageServerState::AlreadyConfigured
- : PythonLanguageServerState::ConfiguredButDisabled,
- FilePath()};
- }
- }
-
- QtcProcess pythonProcess;
- pythonProcess.setCommand(pythonLShelpCommand);
- pythonProcess.runBlocking();
- if (pythonProcess.allOutput().contains("Python Language Server"))
- return {PythonLanguageServerState::AlreadyInstalled, modulePath};
-
- pythonProcess.setCommand({python, {"-m", "pip", "-V"}});
- pythonProcess.runBlocking();
- if (pythonProcess.allOutput().startsWith("pip "))
- return {PythonLanguageServerState::CanBeInstalled, FilePath()};
- else
- return {PythonLanguageServerState::CanNotBeInstalled, FilePath()};
-}
-
-static FilePath detectPython(const FilePath &documentPath)
+FilePath detectPython(const FilePath &documentPath)
{
FilePath python;
@@ -207,292 +78,6 @@ static FilePath detectPython(const FilePath &documentPath)
return python;
}
-PyLSConfigureAssistant *PyLSConfigureAssistant::instance()
-{
- static auto *instance = new PyLSConfigureAssistant(PythonPlugin::instance());
- return instance;
-}
-
-const StdIOSettings *PyLSConfigureAssistant::languageServerForPython(const FilePath &python)
-{
- return findOrDefault(configuredPythonLanguageServer(),
- [pythonModulePath = getPylsModulePath(
- CommandLine(python, {"-m", "pylsp"}))](const StdIOSettings *setting) {
- return getPylsModulePath(setting->command()) == pythonModulePath;
- });
-}
-
-static Client *registerLanguageServer(const FilePath &python)
-{
- auto *settings = new StdIOSettings();
- settings->m_executable = python;
- settings->m_arguments = "-m pylsp";
- settings->m_name = PyLSConfigureAssistant::tr("Python Language Server (%1)")
- .arg(pythonName(python));
- settings->m_languageFilter.mimeTypes = QStringList(Constants::C_PY_MIMETYPE);
- LanguageClientManager::registerClientSettings(settings);
- Client *client = LanguageClientManager::clientForSetting(settings).value(0);
- PyLSConfigureAssistant::updateEditorInfoBars(python, client);
- return client;
-}
-
-class PythonLSInstallHelper : public QObject
-{
- Q_OBJECT
-public:
- PythonLSInstallHelper(const FilePath &python, QPointer<TextEditor::TextDocument> 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,
- &QtcProcess::finished,
- this,
- &PythonLSInstallHelper::installFinished);
- connect(&m_process,
- &QtcProcess::readyReadStandardError,
- this,
- &PythonLSInstallHelper::errorAvailable);
- connect(&m_process,
- &QtcProcess::readyReadStandardOutput,
- this,
- &PythonLSInstallHelper::outputAvailable);
-
- connect(&m_killTimer, &QTimer::timeout, this, &PythonLSInstallHelper::cancel);
- connect(&m_watcher, &QFutureWatcher<void>::canceled, this, &PythonLSInstallHelper::cancel);
-
- QStringList arguments = {"-m", "pip", "install", "python-lsp-server[all]"};
-
- // add --user to global pythons, but skip it for venv pythons
- if (!QDir(m_python.parentDir().toString()).exists("activate"))
- arguments << "--user";
-
- m_process.setCommand({m_python, arguments});
- m_process.start();
-
- Core::MessageManager::writeDisrupting(
- tr("Running \"%1\" to install Python language server.")
- .arg(m_process.commandLine().toUserOutput()));
-
- m_killTimer.setSingleShot(true);
- m_killTimer.start(5 /*minutes*/ * 60 * 1000);
- }
-
-private:
- void cancel()
- {
- m_process.stopProcess();
- Core::MessageManager::writeFlashing(
- tr("The Python language server installation was canceled by %1.")
- .arg(m_killTimer.isActive() ? tr("user") : tr("time out")));
- }
-
- void installFinished()
- {
- m_future.reportFinished();
- if (m_process.result() == QtcProcess::FinishedWithSuccess) {
- if (Client *client = registerLanguageServer(m_python))
- LanguageClientManager::openDocumentWithClient(m_document, client);
- } else {
- Core::MessageManager::writeFlashing(
- tr("Installing the Python language server failed with exit code %1")
- .arg(m_process.exitCode()));
- }
- deleteLater();
- }
-
- void outputAvailable()
- {
- const QString &stdOut = QString::fromLocal8Bit(m_process.readAllStandardOutput().trimmed());
- if (!stdOut.isEmpty())
- Core::MessageManager::writeSilently(stdOut);
- }
-
- void errorAvailable()
- {
- const QString &stdErr = QString::fromLocal8Bit(m_process.readAllStandardError().trimmed());
- if (!stdErr.isEmpty())
- Core::MessageManager::writeSilently(stdErr);
- }
-
- QFutureInterface<void> m_future;
- QFutureWatcher<void> m_watcher;
- QtcProcess m_process;
- QTimer m_killTimer;
- const FilePath m_python;
- QPointer<TextEditor::TextDocument> m_document;
-};
-
-void PyLSConfigureAssistant::installPythonLanguageServer(const FilePath &python,
- QPointer<TextEditor::TextDocument> document)
-{
- document->infoBar()->removeInfo(installPylsInfoBarId);
-
- // Hide all install info bar entries for this python, but keep them in the list
- // so the language server will be setup properly after the installation is done.
- for (TextEditor::TextDocument *additionalDocument : m_infoBarEntries[python])
- additionalDocument->infoBar()->removeInfo(installPylsInfoBarId);
-
- auto install = new PythonLSInstallHelper(python, document);
- install->run();
-}
-
-static void setupPythonLanguageServer(const FilePath &python,
- QPointer<TextEditor::TextDocument> document)
-{
- document->infoBar()->removeInfo(startPylsInfoBarId);
- if (Client *client = registerLanguageServer(python))
- LanguageClientManager::openDocumentWithClient(document, client);
-}
-
-static void enablePythonLanguageServer(const FilePath &python,
- QPointer<TextEditor::TextDocument> document)
-{
- document->infoBar()->removeInfo(enablePylsInfoBarId);
- if (const StdIOSettings *setting = PyLSConfigureAssistant::languageServerForPython(python)) {
- LanguageClientManager::enableClientSettings(setting->m_id);
- if (const StdIOSettings *setting = PyLSConfigureAssistant::languageServerForPython(python)) {
- if (Client *client = LanguageClientManager::clientForSetting(setting).value(0)) {
- LanguageClientManager::openDocumentWithClient(document, client);
- PyLSConfigureAssistant::updateEditorInfoBars(python, client);
- }
- }
- }
-}
-
-void PyLSConfigureAssistant::documentOpened(Core::IDocument *document)
-{
- auto textDocument = qobject_cast<TextEditor::TextDocument *>(document);
- if (!textDocument || textDocument->mimeType() != Constants::C_PY_MIMETYPE)
- return;
-
- const FilePath &python = detectPython(textDocument->filePath());
- if (!python.exists())
- return;
-
- instance()->openDocumentWithPython(python, textDocument);
-}
-
-void PyLSConfigureAssistant::openDocumentWithPython(const FilePath &python,
- TextEditor::TextDocument *document)
-{
- using CheckPylsWatcher = QFutureWatcher<PythonLanguageServerState>;
-
- QPointer<CheckPylsWatcher> watcher = new CheckPylsWatcher();
-
- // cancel and delete watcher after a 10 second timeout
- QTimer::singleShot(10000, this, [watcher]() {
- if (watcher) {
- watcher->cancel();
- watcher->deleteLater();
- }
- });
-
- connect(
- watcher,
- &CheckPylsWatcher::resultReadyAt,
- this,
- [=, document = QPointer<TextEditor::TextDocument>(document)]() {
- if (!document || !watcher)
- return;
- handlePyLSState(python, watcher->result(), document);
- watcher->deleteLater();
- });
- watcher->setFuture(Utils::runAsync(&checkPythonLanguageServer, python));
-}
-
-void PyLSConfigureAssistant::handlePyLSState(const FilePath &python,
- const PythonLanguageServerState &state,
- TextEditor::TextDocument *document)
-{
- if (state.state == PythonLanguageServerState::CanNotBeInstalled)
- return;
- if (state.state == PythonLanguageServerState::AlreadyConfigured) {
- if (const StdIOSettings *setting = languageServerForPython(python)) {
- if (Client *client = LanguageClientManager::clientForSetting(setting).value(0))
- LanguageClientManager::openDocumentWithClient(document, client);
- }
- return;
- }
-
- resetEditorInfoBar(document);
- Utils::InfoBar *infoBar = document->infoBar();
- if (state.state == PythonLanguageServerState::CanBeInstalled
- && infoBar->canInfoBeAdded(installPylsInfoBarId)) {
- auto message = tr("Install and set up 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);
- info.setCustomButtonInfo(tr("Install"),
- [=]() { installPythonLanguageServer(python, document); });
- infoBar->addInfo(info);
- m_infoBarEntries[python] << document;
- } else if (state.state == PythonLanguageServerState::AlreadyInstalled
- && infoBar->canInfoBeAdded(startPylsInfoBarId)) {
- auto message = tr("Found a Python language server for %1 (%2). "
- "Set it up for this document?")
- .arg(pythonName(python), python.toUserOutput());
- Utils::InfoBarEntry info(startPylsInfoBarId,
- message,
- Utils::InfoBarEntry::GlobalSuppression::Enabled);
- info.setCustomButtonInfo(tr("Set Up"),
- [=]() { setupPythonLanguageServer(python, document); });
- infoBar->addInfo(info);
- m_infoBarEntries[python] << document;
- } else if (state.state == PythonLanguageServerState::ConfiguredButDisabled
- && infoBar->canInfoBeAdded(enablePylsInfoBarId)) {
- auto message = tr("Enable Python language server for %1 (%2)?")
- .arg(pythonName(python), python.toUserOutput());
- Utils::InfoBarEntry info(enablePylsInfoBarId,
- message,
- Utils::InfoBarEntry::GlobalSuppression::Enabled);
- info.setCustomButtonInfo(tr("Enable"),
- [=]() { enablePythonLanguageServer(python, document); });
- infoBar->addInfo(info);
- m_infoBarEntries[python] << document;
- }
-}
-
-void PyLSConfigureAssistant::updateEditorInfoBars(const FilePath &python, Client *client)
-{
- for (TextEditor::TextDocument *document : instance()->m_infoBarEntries.take(python)) {
- instance()->resetEditorInfoBar(document);
- if (client)
- LanguageClientManager::openDocumentWithClient(document, client);
- }
-}
-
-void PyLSConfigureAssistant::resetEditorInfoBar(TextEditor::TextDocument *document)
-{
- for (QList<TextEditor::TextDocument *> &documents : m_infoBarEntries)
- documents.removeAll(document);
- Utils::InfoBar *infoBar = document->infoBar();
- infoBar->removeInfo(installPylsInfoBarId);
- infoBar->removeInfo(startPylsInfoBarId);
- infoBar->removeInfo(enablePylsInfoBarId);
-}
-
-PyLSConfigureAssistant::PyLSConfigureAssistant(QObject *parent)
- : QObject(parent)
-{
- Core::EditorManager::instance();
-
- connect(Core::EditorManager::instance(),
- &Core::EditorManager::documentClosed,
- this,
- [this](Core::IDocument *document) {
- if (auto textDocument = qobject_cast<TextEditor::TextDocument *>(document))
- resetEditorInfoBar(textDocument);
- });
-}
-
static QStringList replImportArgs(const FilePath &pythonFile, ReplType type)
{
using MimeTypes = QList<MimeType>;
@@ -543,5 +128,3 @@ void openPythonRepl(const FilePath &file, ReplType type)
} // namespace Internal
} // namespace Python
-
-#include "pythonutils.moc"