Python: add pyside uic extra compiler

To be able to autocomplete code from .ui forms we need to feed the
python language server the compiled form. The uic extra compiler
generates a temporary ui_name.py file for uncompiled or unsaved ui
files. These files are inside a folder that gets appended to the python
path environment variable for the python language server.

Change-Id: I9f48d2012162f33986639315189c41e0a7e0dad2
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2022-05-25 15:24:09 +02:00
parent d8b404a1a3
commit b9627ddba7
11 changed files with 366 additions and 35 deletions

View File

@@ -26,18 +26,24 @@
#include "pythonlanguageclient.h"
#include "pipsupport.h"
#include "pysideuicextracompiler.h"
#include "pythonconstants.h"
#include "pythonplugin.h"
#include "pythonproject.h"
#include "pythonrunconfiguration.h"
#include "pythonsettings.h"
#include "pythonutils.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <languageclient/languageclientinterface.h>
#include <languageclient/languageclientmanager.h>
#include <languageserverprotocol/textsynchronization.h>
#include <languageserverprotocol/workspace.h>
#include <projectexplorer/extracompiler.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <utils/infobar.h>
@@ -57,6 +63,7 @@
#include <QTimer>
using namespace LanguageClient;
using namespace LanguageServerProtocol;
using namespace ProjectExplorer;
using namespace Utils;
@@ -67,8 +74,9 @@ static constexpr char startPylsInfoBarId[] = "Python::StartPyls";
static constexpr char installPylsInfoBarId[] = "Python::InstallPyls";
static constexpr char enablePylsInfoBarId[] = "Python::EnablePyls";
struct PythonLanguageServerState
class PythonLanguageServerState
{
public:
enum {
CanNotBeInstalled,
CanBeInstalled,
@@ -454,35 +462,119 @@ void PyLSSettings::setInterpreter(const QString &interpreterId)
m_executable = interpreter.command;
}
class PyLSClient : public Client
class PyLSInterface : public StdIOClientInterface
{
public:
using Client::Client;
void openDocument(TextEditor::TextDocument *document) override
PyLSInterface()
: m_extraPythonPath("QtCreator-pyls-XXXXXX")
{
using namespace LanguageServerProtocol;
if (reachable()) {
const FilePath documentPath = document->filePath();
if (isSupportedDocument(document) && !pythonProjectForFile(documentPath)) {
const FilePath workspacePath = documentPath.parentDir();
if (!extraWorkspaceDirs.contains(workspacePath)) {
WorkspaceFoldersChangeEvent event;
event.setAdded({WorkSpaceFolder(DocumentUri::fromFilePath(workspacePath),
workspacePath.fileName())});
DidChangeWorkspaceFoldersParams params;
params.setEvent(event);
DidChangeWorkspaceFoldersNotification change(params);
sendMessage(change);
extraWorkspaceDirs.append(workspacePath);
}
Environment env = Environment::systemEnvironment();
env.appendOrSet("PYTHONPATH",
m_extraPythonPath.path().toString(),
OsSpecificAspects::pathListSeparator(env.osType()));
setEnvironment(env);
}
TemporaryDirectory m_extraPythonPath;
};
BaseClientInterface *PyLSSettings::createInterfaceWithProject(
ProjectExplorer::Project *project) const
{
auto interface = new PyLSInterface;
interface->setCommandLine(command());
if (project)
interface->setWorkingDirectory(project->projectDirectory());
return interface;
}
PyLSClient::PyLSClient(BaseClientInterface *interface)
: Client(interface)
, m_extraCompilerOutputDir(static_cast<PyLSInterface *>(interface)->m_extraPythonPath.path())
{
}
void PyLSClient::openDocument(TextEditor::TextDocument *document)
{
using namespace LanguageServerProtocol;
if (reachable()) {
const FilePath documentPath = document->filePath();
if (PythonProject *project = pythonProjectForFile(documentPath)) {
if (Target *target = project->activeTarget()) {
if (auto rc = qobject_cast<PythonRunConfiguration *>(target->activeRunConfiguration()))
updateExtraCompilers(project, rc->extraCompilers());
}
} else if (isSupportedDocument(document)) {
const FilePath workspacePath = documentPath.parentDir();
if (!m_extraWorkspaceDirs.contains(workspacePath)) {
WorkspaceFoldersChangeEvent event;
event.setAdded({WorkSpaceFolder(DocumentUri::fromFilePath(workspacePath),
workspacePath.fileName())});
DidChangeWorkspaceFoldersParams params;
params.setEvent(event);
DidChangeWorkspaceFoldersNotification change(params);
sendMessage(change);
m_extraWorkspaceDirs.append(workspacePath);
}
}
Client::openDocument(document);
}
Client::openDocument(document);
}
private:
FilePaths extraWorkspaceDirs;
};
void PyLSClient::projectClosed(ProjectExplorer::Project *project)
{
for (ProjectExplorer::ExtraCompiler *compiler : m_extraCompilers.value(project))
closeExtraCompiler(compiler);
Client::projectClosed(project);
}
void PyLSClient::updateExtraCompilers(ProjectExplorer::Project *project,
const QList<PySideUicExtraCompiler *> &extraCompilers)
{
auto oldCompilers = m_extraCompilers.take(project);
for (PySideUicExtraCompiler *extraCompiler : extraCompilers) {
QTC_ASSERT(extraCompiler->targets().size() == 1 , continue);
int index = oldCompilers.indexOf(extraCompiler);
if (index < 0) {
m_extraCompilers[project] << extraCompiler;
connect(extraCompiler,
&ExtraCompiler::contentsChanged,
this,
[this, extraCompiler](const FilePath &file) {
updateExtraCompilerContents(extraCompiler, file);
});
if (extraCompiler->isDirty())
static_cast<ExtraCompiler *>(extraCompiler)->run();
} else {
m_extraCompilers[project] << oldCompilers.takeAt(index);
}
}
for (ProjectExplorer::ExtraCompiler *compiler : oldCompilers)
closeExtraCompiler(compiler);
}
void PyLSClient::updateExtraCompilerContents(ExtraCompiler *compiler, const FilePath &file)
{
const QString text = QString::fromUtf8(compiler->content(file));
const FilePath target = m_extraCompilerOutputDir.pathAppended(file.fileName());
target.writeFileContents(compiler->content(file));
}
void PyLSClient::closeExtraCompiler(ProjectExplorer::ExtraCompiler *compiler)
{
const FilePath file = compiler->targets().first();
m_extraCompilerOutputDir.pathAppended(file.fileName()).removeFile();
compiler->disconnect(this);
}
PyLSClient *PyLSClient::clientForPython(const FilePath &python)
{
if (auto setting = PyLSConfigureAssistant::languageServerForPython(python)) {
if (auto client = LanguageClientManager::clientsForSetting(setting).value(0))
return qobject_cast<PyLSClient *>(client);
}
return nullptr;
}
Client *PyLSSettings::createClient(BaseClientInterface *interface) const
{