2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2021 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2021-02-11 15:21:23 +01:00
|
|
|
|
2021-02-22 12:09:52 +01:00
|
|
|
#include "androidconfigurations.h"
|
2021-02-11 15:21:23 +01:00
|
|
|
#include "androidconstants.h"
|
2021-02-22 12:09:52 +01:00
|
|
|
#include "androidmanager.h"
|
2022-10-06 17:53:35 +02:00
|
|
|
#include "androidtr.h"
|
|
|
|
|
#include "javalanguageserver.h"
|
2021-02-11 15:21:23 +01:00
|
|
|
|
2021-02-12 14:05:10 +01:00
|
|
|
#include <languageclient/client.h>
|
2021-02-19 08:57:45 +01:00
|
|
|
#include <languageclient/languageclientinterface.h>
|
2021-02-12 14:05:10 +01:00
|
|
|
#include <languageclient/languageclientutils.h>
|
2023-08-11 09:18:56 +02:00
|
|
|
#include <projectexplorer/kitaspects.h>
|
2021-02-22 12:09:52 +01:00
|
|
|
#include <projectexplorer/project.h>
|
|
|
|
|
#include <projectexplorer/projectnodes.h>
|
|
|
|
|
#include <projectexplorer/target.h>
|
2023-08-11 11:23:12 +02:00
|
|
|
#include <qtsupport/qtkitaspect.h>
|
2021-02-11 15:21:23 +01:00
|
|
|
#include <utils/environment.h>
|
|
|
|
|
#include <utils/pathchooser.h>
|
2021-02-19 08:57:45 +01:00
|
|
|
#include <utils/temporarydirectory.h>
|
2021-02-11 15:21:23 +01:00
|
|
|
#include <utils/variablechooser.h>
|
|
|
|
|
|
|
|
|
|
#include <QGridLayout>
|
|
|
|
|
#include <QLineEdit>
|
2022-05-24 00:40:44 +02:00
|
|
|
#include <QXmlStreamWriter>
|
2021-02-11 15:21:23 +01:00
|
|
|
|
2021-09-27 12:58:46 +02:00
|
|
|
using namespace ProjectExplorer;
|
2021-02-11 15:21:23 +01:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
constexpr char languageServerKey[] = "languageServer";
|
|
|
|
|
|
|
|
|
|
namespace Android {
|
|
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
class JLSSettingsWidget : public QWidget
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
JLSSettingsWidget(const JLSSettings *settings, QWidget *parent);
|
|
|
|
|
|
|
|
|
|
QString name() const { return m_name->text(); }
|
2021-08-31 13:34:20 +02:00
|
|
|
FilePath java() const { return m_java->filePath(); }
|
2021-09-27 11:13:56 +02:00
|
|
|
FilePath languageServer() const { return m_ls->filePath(); }
|
2021-02-11 15:21:23 +01:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QLineEdit *m_name = nullptr;
|
|
|
|
|
PathChooser *m_java = nullptr;
|
|
|
|
|
PathChooser *m_ls = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
JLSSettingsWidget::JLSSettingsWidget(const JLSSettings *settings, QWidget *parent)
|
|
|
|
|
: QWidget(parent)
|
|
|
|
|
, m_name(new QLineEdit(settings->m_name, this))
|
|
|
|
|
, m_java(new PathChooser(this))
|
|
|
|
|
, m_ls(new PathChooser(this))
|
|
|
|
|
{
|
|
|
|
|
int row = 0;
|
|
|
|
|
auto *mainLayout = new QGridLayout;
|
2022-10-06 17:53:35 +02:00
|
|
|
mainLayout->addWidget(new QLabel(Tr::tr("Name:")), row, 0);
|
2021-02-11 15:21:23 +01:00
|
|
|
mainLayout->addWidget(m_name, row, 1);
|
|
|
|
|
auto chooser = new VariableChooser(this);
|
|
|
|
|
chooser->addSupportedWidget(m_name);
|
|
|
|
|
|
2022-10-06 17:53:35 +02:00
|
|
|
mainLayout->addWidget(new QLabel(Tr::tr("Java:")), ++row, 0);
|
2021-09-27 12:58:46 +02:00
|
|
|
m_java->setExpectedKind(PathChooser::ExistingCommand);
|
2021-08-31 13:34:20 +02:00
|
|
|
m_java->setFilePath(settings->m_executable);
|
2021-02-11 15:21:23 +01:00
|
|
|
mainLayout->addWidget(m_java, row, 1);
|
|
|
|
|
|
2022-10-06 17:53:35 +02:00
|
|
|
mainLayout->addWidget(new QLabel(Tr::tr("Java Language Server:")), ++row, 0);
|
2021-09-27 12:58:46 +02:00
|
|
|
m_ls->setExpectedKind(PathChooser::File);
|
2022-10-06 17:53:35 +02:00
|
|
|
m_ls->lineEdit()->setPlaceholderText(Tr::tr("Path to equinox launcher jar"));
|
2021-02-11 15:21:23 +01:00
|
|
|
m_ls->setPromptDialogFilter("org.eclipse.equinox.launcher_*.jar");
|
2021-09-27 11:13:56 +02:00
|
|
|
m_ls->setFilePath(settings->m_languageServer);
|
2021-02-11 15:21:23 +01:00
|
|
|
mainLayout->addWidget(m_ls, row, 1);
|
|
|
|
|
|
|
|
|
|
setLayout(mainLayout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JLSSettings::JLSSettings()
|
|
|
|
|
{
|
|
|
|
|
m_settingsTypeId = Constants::JLS_SETTINGS_ID;
|
|
|
|
|
m_name = "Java Language Server";
|
|
|
|
|
m_startBehavior = RequiresProject;
|
|
|
|
|
m_languageFilter.mimeTypes = QStringList(Constants::JAVA_MIMETYPE);
|
2021-09-27 12:58:46 +02:00
|
|
|
const FilePath &javaPath = Environment::systemEnvironment().searchInPath("java");
|
2021-02-11 15:21:23 +01:00
|
|
|
if (javaPath.exists())
|
2021-08-31 13:34:20 +02:00
|
|
|
m_executable = javaPath;
|
2021-02-11 15:21:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool JLSSettings::applyFromSettingsWidget(QWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
bool changed = false;
|
|
|
|
|
auto jlswidget = static_cast<JLSSettingsWidget *>(widget);
|
|
|
|
|
changed |= m_name != jlswidget->name();
|
|
|
|
|
m_name = jlswidget->name();
|
|
|
|
|
|
|
|
|
|
changed |= m_languageServer != jlswidget->languageServer();
|
|
|
|
|
m_languageServer = jlswidget->languageServer();
|
|
|
|
|
|
|
|
|
|
changed |= m_executable != jlswidget->java();
|
|
|
|
|
m_executable = jlswidget->java();
|
|
|
|
|
|
|
|
|
|
QString arguments = "-Declipse.application=org.eclipse.jdt.ls.core.id1 "
|
|
|
|
|
"-Dosgi.bundles.defaultStartLevel=4 "
|
|
|
|
|
"-Declipse.product=org.eclipse.jdt.ls.core.product "
|
|
|
|
|
"-Dlog.level=WARNING "
|
|
|
|
|
"-noverify "
|
|
|
|
|
"-Xmx1G "
|
|
|
|
|
"-jar \"%1\" "
|
2021-02-19 08:57:45 +01:00
|
|
|
"-configuration \"%2\"";
|
2021-02-11 15:21:23 +01:00
|
|
|
|
2021-09-27 11:13:56 +02:00
|
|
|
QDir configDir = m_languageServer.toFileInfo().absoluteDir();
|
2021-02-11 15:21:23 +01:00
|
|
|
if (configDir.exists()) {
|
|
|
|
|
configDir.cdUp();
|
|
|
|
|
if constexpr (HostOsInfo::hostOs() == OsTypeWindows)
|
|
|
|
|
configDir.cd("config_win");
|
|
|
|
|
else if constexpr (HostOsInfo::hostOs() == OsTypeLinux)
|
|
|
|
|
configDir.cd("config_linux");
|
|
|
|
|
else if constexpr (HostOsInfo::hostOs() == OsTypeMac)
|
|
|
|
|
configDir.cd("config_mac");
|
|
|
|
|
}
|
|
|
|
|
if (configDir.exists()) {
|
2021-09-27 11:13:56 +02:00
|
|
|
arguments = arguments.arg(m_languageServer.toString(), configDir.absolutePath());
|
2021-02-11 15:21:23 +01:00
|
|
|
changed |= m_arguments != arguments;
|
|
|
|
|
m_arguments = arguments;
|
|
|
|
|
}
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QWidget *JLSSettings::createSettingsWidget(QWidget *parent) const
|
|
|
|
|
{
|
|
|
|
|
return new JLSSettingsWidget(this, parent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool JLSSettings::isValid() const
|
|
|
|
|
{
|
2021-02-19 08:57:45 +01:00
|
|
|
return StdIOSettings::isValid() && !m_languageServer.isEmpty();
|
2021-02-11 15:21:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariantMap JLSSettings::toMap() const
|
|
|
|
|
{
|
|
|
|
|
QVariantMap map = StdIOSettings::toMap();
|
2023-01-03 12:31:52 +01:00
|
|
|
map.insert(languageServerKey, m_languageServer.toSettings());
|
2021-02-11 15:21:23 +01:00
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JLSSettings::fromMap(const QVariantMap &map)
|
|
|
|
|
{
|
|
|
|
|
StdIOSettings::fromMap(map);
|
2023-01-03 12:31:52 +01:00
|
|
|
m_languageServer = FilePath::fromSettings(map[languageServerKey]);
|
2021-02-11 15:21:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LanguageClient::BaseSettings *JLSSettings::copy() const
|
|
|
|
|
{
|
|
|
|
|
return new JLSSettings(*this);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-19 08:57:45 +01:00
|
|
|
class JLSInterface : public LanguageClient::StdIOClientInterface
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
JLSInterface() = default;
|
|
|
|
|
|
2021-07-01 09:58:48 +02:00
|
|
|
QString workspaceDir() const { return m_workspaceDir.path().path(); }
|
2021-02-19 08:57:45 +01:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
TemporaryDirectory m_workspaceDir = TemporaryDirectory("QtCreator-jls-XXXXXX");
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-03 13:50:31 +02:00
|
|
|
LanguageClient::BaseClientInterface *JLSSettings::createInterface(ProjectExplorer::Project *) const
|
2021-02-19 08:57:45 +01:00
|
|
|
{
|
|
|
|
|
auto interface = new JLSInterface();
|
2021-08-31 13:34:20 +02:00
|
|
|
CommandLine cmd{m_executable, arguments(), CommandLine::Raw};
|
2021-04-30 08:25:10 +02:00
|
|
|
cmd.addArgs({"-data", interface->workspaceDir()});
|
|
|
|
|
interface->setCommandLine(cmd);
|
2021-02-19 08:57:45 +01:00
|
|
|
return interface;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-12 14:05:10 +01:00
|
|
|
class JLSClient : public LanguageClient::Client
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
using Client::Client;
|
|
|
|
|
|
|
|
|
|
void executeCommand(const LanguageServerProtocol::Command &command) override;
|
2021-09-27 12:58:46 +02:00
|
|
|
void setCurrentProject(Project *project) override;
|
2021-02-22 12:09:52 +01:00
|
|
|
void updateProjectFiles();
|
2021-09-27 12:58:46 +02:00
|
|
|
void updateTarget(Target *target);
|
2021-02-22 12:09:52 +01:00
|
|
|
|
|
|
|
|
private:
|
2021-09-27 12:58:46 +02:00
|
|
|
Target *m_currentTarget = nullptr;
|
2021-02-12 14:05:10 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void JLSClient::executeCommand(const LanguageServerProtocol::Command &command)
|
|
|
|
|
{
|
|
|
|
|
if (command.command() == "java.apply.workspaceEdit") {
|
|
|
|
|
const QJsonArray arguments = command.arguments().value_or(QJsonArray());
|
|
|
|
|
for (const QJsonValue &argument : arguments) {
|
|
|
|
|
if (!argument.isObject())
|
|
|
|
|
continue;
|
|
|
|
|
LanguageServerProtocol::WorkspaceEdit edit(argument.toObject());
|
2021-02-26 08:29:15 +01:00
|
|
|
if (edit.isValid())
|
2021-06-14 11:47:04 +02:00
|
|
|
LanguageClient::applyWorkspaceEdit(this, edit);
|
2021-02-12 14:05:10 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Client::executeCommand(command);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-27 12:58:46 +02:00
|
|
|
void JLSClient::setCurrentProject(Project *project)
|
2021-02-22 12:09:52 +01:00
|
|
|
{
|
|
|
|
|
Client::setCurrentProject(project);
|
|
|
|
|
QTC_ASSERT(project, return);
|
|
|
|
|
updateTarget(project->activeTarget());
|
|
|
|
|
updateProjectFiles();
|
2021-09-27 12:58:46 +02:00
|
|
|
connect(project, &Project::activeTargetChanged, this, &JLSClient::updateTarget);
|
2021-02-22 12:09:52 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-23 14:50:01 +01:00
|
|
|
static void generateProjectFile(const FilePath &projectDir,
|
|
|
|
|
const QString &qtSrc,
|
|
|
|
|
const QString &projectName)
|
2021-02-22 12:09:52 +01:00
|
|
|
{
|
|
|
|
|
const FilePath projectFilePath = projectDir.pathAppended(".project");
|
|
|
|
|
QFile projectFile(projectFilePath.toString());
|
|
|
|
|
if (projectFile.open(QFile::Truncate | QFile::WriteOnly)) {
|
|
|
|
|
QXmlStreamWriter writer(&projectFile);
|
|
|
|
|
writer.setAutoFormatting(true);
|
|
|
|
|
writer.writeStartDocument();
|
|
|
|
|
writer.writeComment("Autogenerated by Qt Creator. "
|
|
|
|
|
"Changes to this file will not be taken into account.");
|
|
|
|
|
writer.writeStartElement("projectDescription");
|
|
|
|
|
writer.writeTextElement("name", projectName);
|
|
|
|
|
writer.writeStartElement("natures");
|
|
|
|
|
writer.writeTextElement("nature", "org.eclipse.jdt.core.javanature");
|
|
|
|
|
writer.writeEndElement(); // natures
|
2021-02-23 14:50:01 +01:00
|
|
|
writer.writeStartElement("linkedResources");
|
|
|
|
|
writer.writeStartElement("link");
|
|
|
|
|
writer.writeTextElement("name", "qtSrc");
|
|
|
|
|
writer.writeTextElement("type", "2");
|
|
|
|
|
writer.writeTextElement("location", qtSrc);
|
|
|
|
|
writer.writeEndElement(); // link
|
|
|
|
|
writer.writeEndElement(); // linkedResources
|
2021-02-22 12:09:52 +01:00
|
|
|
writer.writeEndElement(); // projectDescription
|
|
|
|
|
writer.writeEndDocument();
|
|
|
|
|
projectFile.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void generateClassPathFile(const FilePath &projectDir,
|
2021-09-27 11:13:56 +02:00
|
|
|
const FilePath &sourceDir,
|
|
|
|
|
const FilePaths &libs)
|
2021-02-22 12:09:52 +01:00
|
|
|
{
|
|
|
|
|
const FilePath classPathFilePath = projectDir.pathAppended(".classpath");
|
|
|
|
|
QFile classPathFile(classPathFilePath.toString());
|
|
|
|
|
if (classPathFile.open(QFile::Truncate | QFile::WriteOnly)) {
|
|
|
|
|
QXmlStreamWriter writer(&classPathFile);
|
|
|
|
|
writer.setAutoFormatting(true);
|
|
|
|
|
writer.writeStartDocument();
|
|
|
|
|
writer.writeComment("Autogenerated by Qt Creator. "
|
|
|
|
|
"Changes to this file will not be taken into account.");
|
|
|
|
|
writer.writeStartElement("classpath");
|
|
|
|
|
writer.writeEmptyElement("classpathentry");
|
|
|
|
|
writer.writeAttribute("kind", "src");
|
2021-09-27 11:13:56 +02:00
|
|
|
writer.writeAttribute("path", sourceDir.toString());
|
2021-02-23 14:50:01 +01:00
|
|
|
writer.writeEmptyElement("classpathentry");
|
|
|
|
|
writer.writeAttribute("kind", "src");
|
|
|
|
|
writer.writeAttribute("path", "qtSrc");
|
2021-09-27 11:13:56 +02:00
|
|
|
for (const FilePath &lib : libs) {
|
2021-02-22 12:09:52 +01:00
|
|
|
writer.writeEmptyElement("classpathentry");
|
|
|
|
|
writer.writeAttribute("kind", "lib");
|
2021-09-27 11:13:56 +02:00
|
|
|
writer.writeAttribute("path", lib.toString());
|
2021-02-22 12:09:52 +01:00
|
|
|
}
|
|
|
|
|
writer.writeEndElement(); // classpath
|
|
|
|
|
writer.writeEndDocument();
|
|
|
|
|
classPathFile.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void JLSClient::updateProjectFiles()
|
|
|
|
|
{
|
|
|
|
|
if (!m_currentTarget)
|
|
|
|
|
return;
|
|
|
|
|
if (Target *target = m_currentTarget) {
|
|
|
|
|
Kit *kit = m_currentTarget->kit();
|
|
|
|
|
if (DeviceTypeKitAspect::deviceTypeId(kit) != Android::Constants::ANDROID_DEVICE_TYPE)
|
|
|
|
|
return;
|
|
|
|
|
if (ProjectNode *node = project()->findNodeForBuildKey(target->activeBuildKey())) {
|
2022-01-21 16:06:36 +01:00
|
|
|
QtSupport::QtVersion *version = QtSupport::QtKitAspect::qtVersion(kit);
|
2021-02-23 14:50:01 +01:00
|
|
|
if (!version)
|
|
|
|
|
return;
|
|
|
|
|
const QString qtSrc = version->prefix().toString() + "/src/android/java/src";
|
2021-02-22 12:09:52 +01:00
|
|
|
const FilePath &projectDir = project()->rootProjectDirectory();
|
|
|
|
|
if (!projectDir.exists())
|
|
|
|
|
return;
|
2021-02-23 10:30:38 +01:00
|
|
|
const FilePath packageSourceDir = FilePath::fromVariant(
|
2021-02-22 12:09:52 +01:00
|
|
|
node->data(Constants::AndroidPackageSourceDir));
|
2021-02-23 10:30:38 +01:00
|
|
|
FilePath sourceDir = packageSourceDir.pathAppended("src");
|
2021-02-22 12:09:52 +01:00
|
|
|
if (!sourceDir.exists())
|
|
|
|
|
return;
|
|
|
|
|
sourceDir = sourceDir.relativeChildPath(projectDir);
|
|
|
|
|
const FilePath &sdkLocation = AndroidConfigurations::currentConfig().sdkLocation();
|
|
|
|
|
const QString &targetSDK = AndroidManager::buildTargetSDK(m_currentTarget);
|
2021-09-27 11:13:56 +02:00
|
|
|
const FilePath androidJar = sdkLocation / QString("platforms/%2/android.jar")
|
|
|
|
|
.arg(targetSDK);
|
|
|
|
|
FilePaths libs = {androidJar};
|
2022-01-21 12:22:54 +01:00
|
|
|
libs << packageSourceDir.pathAppended("libs").dirEntries({{"*.jar"}, QDir::Files});
|
2021-02-23 14:50:01 +01:00
|
|
|
generateProjectFile(projectDir, qtSrc, project()->displayName());
|
2021-09-27 11:13:56 +02:00
|
|
|
generateClassPathFile(projectDir, sourceDir, libs);
|
2021-02-22 12:09:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-27 12:58:46 +02:00
|
|
|
void JLSClient::updateTarget(Target *target)
|
2021-02-22 12:09:52 +01:00
|
|
|
{
|
2021-09-27 12:58:46 +02:00
|
|
|
if (m_currentTarget)
|
|
|
|
|
disconnect(m_currentTarget, &Target::parsingFinished, this, &JLSClient::updateProjectFiles);
|
|
|
|
|
|
2021-02-22 12:09:52 +01:00
|
|
|
m_currentTarget = target;
|
2021-09-27 12:58:46 +02:00
|
|
|
|
|
|
|
|
if (m_currentTarget)
|
|
|
|
|
connect(m_currentTarget, &Target::parsingFinished, this, &JLSClient::updateProjectFiles);
|
|
|
|
|
|
2021-02-22 12:09:52 +01:00
|
|
|
updateProjectFiles();
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-12 14:05:10 +01:00
|
|
|
LanguageClient::Client *JLSSettings::createClient(LanguageClient::BaseClientInterface *interface) const
|
|
|
|
|
{
|
|
|
|
|
return new JLSClient(interface);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 15:21:23 +01:00
|
|
|
} // namespace Internal
|
|
|
|
|
} // namespace Android
|