CMakePM: Revamp the CMake code completion

By using KSyntaxHighlighting's metadata from the cmake.xml file.

With this information the code completion has localized arguments for
functions.

Added support for Generator Expressions $< and function ${ completions.

The project functions/macros and options are also taken into
consideration.

The file completion is using FilePaths and should work remotely.

Change-Id: I79d1360c1249c65c9db65349f326be5d41f0f734
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Cristian Adam
2023-09-17 20:19:04 +02:00
parent 88ddecd281
commit bc1c24eb84
6 changed files with 449 additions and 30 deletions

View File

@@ -1250,6 +1250,9 @@ void CMakeBuildSystem::setupCMakeSymbolsHash()
{ {
m_cmakeSymbolsHash.clear(); m_cmakeSymbolsHash.clear();
m_projectKeywords.functions.clear();
m_projectKeywords.variables.clear();
for (const auto &cmakeFile : std::as_const(m_cmakeFiles)) { for (const auto &cmakeFile : std::as_const(m_cmakeFiles)) {
for (const auto &func : cmakeFile.cmakeListFile.Functions) { for (const auto &func : cmakeFile.cmakeListFile.Functions) {
if (func.LowerCaseName() != "function" && func.LowerCaseName() != "macro" if (func.LowerCaseName() != "function" && func.LowerCaseName() != "macro"
@@ -1265,8 +1268,17 @@ void CMakeBuildSystem::setupCMakeSymbolsHash()
link.targetLine = arg.Line; link.targetLine = arg.Line;
link.targetColumn = arg.Column - 1; link.targetColumn = arg.Column - 1;
m_cmakeSymbolsHash.insert(QString::fromUtf8(arg.Value), link); m_cmakeSymbolsHash.insert(QString::fromUtf8(arg.Value), link);
if (func.LowerCaseName() == "option")
m_projectKeywords.variables << QString::fromUtf8(arg.Value);
else
m_projectKeywords.functions << QString::fromUtf8(arg.Value);
} }
} }
// Code completion setup
if (CMakeTool *tool = CMakeKitAspect::cmakeTool(target()->kit()))
tool->keywords();
} }
void CMakeBuildSystem::ensureBuildDirectory(const BuildDirParameters &parameters) void CMakeBuildSystem::ensureBuildDirectory(const BuildDirParameters &parameters)

View File

@@ -119,6 +119,7 @@ public:
QString warning() const; QString warning() const;
const QHash<QString, Utils::Link> &cmakeSymbolsHash() const { return m_cmakeSymbolsHash; } const QHash<QString, Utils::Link> &cmakeSymbolsHash() const { return m_cmakeSymbolsHash; }
CMakeKeywords projectKeywords() const { return m_projectKeywords; }
signals: signals:
void configurationCleared(); void configurationCleared();
@@ -223,6 +224,7 @@ private:
QList<CMakeBuildTarget> m_buildTargets; QList<CMakeBuildTarget> m_buildTargets;
QSet<CMakeFileInfo> m_cmakeFiles; QSet<CMakeFileInfo> m_cmakeFiles;
QHash<QString, Utils::Link> m_cmakeSymbolsHash; QHash<QString, Utils::Link> m_cmakeSymbolsHash;
CMakeKeywords m_projectKeywords;
QHash<QString, ProjectFileArgumentPosition> m_filesToBeRenamed; QHash<QString, ProjectFileArgumentPosition> m_filesToBeRenamed;

View File

@@ -3,51 +3,318 @@
#include "cmakefilecompletionassist.h" #include "cmakefilecompletionassist.h"
#include "cmakebuildsystem.h"
#include "cmakebuildtarget.h"
#include "cmakekitaspect.h" #include "cmakekitaspect.h"
#include "cmakeproject.h"
#include "cmakeprojectconstants.h" #include "cmakeprojectconstants.h"
#include "cmaketool.h" #include "cmaketool.h"
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectexplorericons.h>
#include <projectexplorer/projectmanager.h> #include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <texteditor/codeassist/assistinterface.h> #include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/genericproposal.h>
#include <texteditor/texteditorsettings.h>
#include <utils/fsengine/fileiconprovider.h>
#include <utils/utilsicons.h>
using namespace TextEditor; using namespace TextEditor;
using namespace ProjectExplorer; using namespace ProjectExplorer;
using namespace Utils;
namespace CMakeProjectManager::Internal { namespace CMakeProjectManager::Internal {
class CMakeFileCompletionAssist : public KeywordsCompletionAssistProcessor class CMakeFileCompletionAssist : public AsyncProcessor
{ {
public: public:
CMakeFileCompletionAssist(); CMakeFileCompletionAssist();
IAssistProposal *performAsync() final; IAssistProposal *performAsync() final;
const QIcon m_variableIcon;
const QIcon m_projectVariableIcon;
const QIcon m_functionIcon;
const QIcon m_projectFunctionIcon;
const QIcon m_propertyIcon;
const QIcon m_argsIcon;
const QIcon m_genexIcon;
const QIcon m_moduleIcon;
const QIcon m_targetsIcon;
TextEditor::SnippetAssistCollector m_snippetCollector;
}; };
CMakeFileCompletionAssist::CMakeFileCompletionAssist() : CMakeFileCompletionAssist::CMakeFileCompletionAssist()
KeywordsCompletionAssistProcessor(Keywords()) : m_variableIcon(CodeModelIcon::iconForType(CodeModelIcon::VarPublic))
, m_projectVariableIcon(CodeModelIcon::iconForType(CodeModelIcon::VarPublicStatic))
, m_functionIcon(CodeModelIcon::iconForType(CodeModelIcon::FuncPublic))
, m_projectFunctionIcon(CodeModelIcon::iconForType(CodeModelIcon::FuncPublicStatic))
, m_propertyIcon(CodeModelIcon::iconForType(CodeModelIcon::Property))
, m_argsIcon(CodeModelIcon::iconForType(CodeModelIcon::Enum))
, m_genexIcon(CodeModelIcon::iconForType(CodeModelIcon::Class))
, m_moduleIcon(
ProjectExplorer::DirectoryIcon(ProjectExplorer::Constants::FILEOVERLAY_MODULES).icon())
, m_targetsIcon(ProjectExplorer::Icons::BUILD.icon())
, m_snippetCollector(Constants::CMAKE_SNIPPETS_GROUP_ID,
CodeModelIcon::iconForType(CodeModelIcon::Keyword))
{}
static bool isInComment(const AssistInterface *interface)
{ {
setSnippetGroup(Constants::CMAKE_SNIPPETS_GROUP_ID); QTextCursor tc(interface->textDocument());
setDynamicCompletionFunction(&TextEditor::pathComplete); tc.setPosition(interface->position());
tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
return tc.selectedText().contains('#');
}
static bool isValidIdentifierChar(const QChar &chr)
{
return chr.isLetterOrNumber() || chr == '_' || chr == '-';
}
static int findWordStart(const AssistInterface *interface, int pos)
{
// Find start position
QChar chr;
do {
chr = interface->characterAt(--pos);
} while (pos > 0 && isValidIdentifierChar(chr));
return ++pos;
}
static int findFunctionStart(const AssistInterface *interface)
{
int pos = interface->position();
QChar chr;
do {
chr = interface->characterAt(--pos);
} while (pos > 0 && chr != '(');
if (pos > 0 && chr == '(') {
// allow space between function name and (
do {
chr = interface->characterAt(--pos);
} while (pos > 0 && chr.isSpace());
++pos;
}
return pos;
}
static int findFunctionEnd(const AssistInterface *interface)
{
int pos = interface->position();
QChar chr;
do {
chr = interface->characterAt(--pos);
} while (pos > 0 && chr != ')');
return pos;
}
static int findPathStart(const AssistInterface *interface)
{
// For pragmatic reasons, we don't support spaces in file names here.
static const auto canOccurInFilePath = [](const QChar &c) {
return c.isLetterOrNumber() || c == '.' || c == '/' || c == '_' || c == '-';
};
int pos = interface->position();
QChar chr;
// Skip to the start of a name
do {
chr = interface->characterAt(--pos);
} while (canOccurInFilePath(chr));
return ++pos;
}
QList<AssistProposalItemInterface *> generateList(const QStringList &words, const QIcon &icon)
{
return transform(words, [&icon](const QString &word) -> AssistProposalItemInterface * {
AssistProposalItem *item = new AssistProposalItem();
item->setText(word);
item->setIcon(icon);
return item;
});
}
static int addFilePathItems(const AssistInterface *interface,
QList<AssistProposalItemInterface *> &items,
int symbolStartPos)
{
if (interface->filePath().isEmpty())
return symbolStartPos;
const int startPos = findPathStart(interface);
if (interface->reason() == IdleEditor
&& interface->position() - startPos
< TextEditorSettings::completionSettings().m_characterThreshold)
return symbolStartPos;
const QString word = interface->textAt(startPos, interface->position() - startPos);
FilePath baseDir = interface->filePath().absoluteFilePath().parentDir();
const int lastSlashPos = word.lastIndexOf(QLatin1Char('/'));
QString prefix = word;
if (lastSlashPos != -1) {
prefix = word.mid(lastSlashPos + 1);
baseDir = baseDir.pathAppended(word.left(lastSlashPos));
}
const FilePaths filesPaths = baseDir.dirEntries(
FileFilter({QString("%1*").arg(prefix)}, QDir::AllEntries | QDir::NoDotAndDotDot));
for (const auto &file : filesPaths) {
AssistProposalItem *item = new AssistProposalItem;
QString fileName = file.fileName();
if (file.isDir())
fileName.append("/");
item->setText(fileName);
item->setIcon(FileIconProvider::icon(file));
items << item;
}
return startPos;
} }
IAssistProposal *CMakeFileCompletionAssist::performAsync() IAssistProposal *CMakeFileCompletionAssist::performAsync()
{ {
Keywords kw; CMakeKeywords keywords;
const Utils::FilePath &filePath = interface()->filePath(); CMakeKeywords projectKeywords;
Project *project = nullptr;
const FilePath &filePath = interface()->filePath();
if (!filePath.isEmpty() && filePath.isFile()) { if (!filePath.isEmpty() && filePath.isFile()) {
Project *p = ProjectManager::projectForFile(filePath); project = static_cast<CMakeProject *>(ProjectManager::projectForFile(filePath));
if (p && p->activeTarget()) { if (project && project->activeTarget()) {
CMakeTool *cmake = CMakeKitAspect::cmakeTool(p->activeTarget()->kit()); CMakeTool *cmake = CMakeKitAspect::cmakeTool(project->activeTarget()->kit());
if (cmake && cmake->isValid()) if (cmake && cmake->isValid())
kw = cmake->keywords(); keywords = cmake->keywords();
} }
} }
setKeywords(kw); QStringList buildTargets;
return KeywordsCompletionAssistProcessor::performAsync(); if (project && project->activeTarget()) {
const auto bs = qobject_cast<CMakeBuildSystem *>(project->activeTarget()->buildSystem());
if (bs) {
for (const auto &target : std::as_const(bs->buildTargets()))
if (target.targetType != TargetType::UtilityType)
buildTargets << target.title;
projectKeywords = bs->projectKeywords();
}
}
if (isInComment(interface()))
return nullptr;
const int startPos = findWordStart(interface(), interface()->position());
const int functionStart = findFunctionStart(interface());
const int prevFunctionEnd = findFunctionEnd(interface());
QString functionName;
if (functionStart > prevFunctionEnd) {
int functionStartPos = findWordStart(interface(), functionStart);
functionName
= interface()->textAt(functionStartPos, functionStart - functionStartPos);
}
if (interface()->reason() == IdleEditor) {
const QChar chr = interface()->characterAt(interface()->position());
const int wordSize = interface()->position() - startPos;
if (isValidIdentifierChar(chr)
|| wordSize < TextEditorSettings::completionSettings().m_characterThreshold) {
return nullptr;
}
}
QList<AssistProposalItemInterface *> items;
const QString varGenexToken = interface()->textAt(startPos - 2, 2);
if (varGenexToken == "${" || varGenexToken == "$<") {
if (varGenexToken == "${") {
items.append(generateList(keywords.variables, m_variableIcon));
items.append(generateList(projectKeywords.variables, m_projectVariableIcon));
}
if (varGenexToken == "$<")
items.append(generateList(keywords.generatorExpressions, m_genexIcon));
return new GenericProposal(startPos, items);
}
int fileStartPos = startPos;
const auto onlyFileItems = [&] { return fileStartPos != startPos; };
if (functionName == "if" || functionName == "elseif" || functionName == "while"
|| functionName == "set" || functionName == "list") {
items.append(generateList(keywords.variables, m_variableIcon));
items.append(generateList(projectKeywords.variables, m_projectVariableIcon));
}
if (functionName.contains("path") || functionName.contains("file")
|| functionName.contains("add_executable") || functionName.contains("add_library")
|| functionName == "include" || functionName == "add_subdirectory"
|| functionName == "install" || functionName == "target_sources" || functionName == "set"
|| functionName == "list") {
fileStartPos = addFilePathItems(interface(), items, startPos);
}
if (functionName == "set_property")
items.append(generateList(keywords.properties, m_propertyIcon));
if (functionName == "set_directory_properties")
items.append(generateList(keywords.directoryProperties, m_propertyIcon));
if (functionName == "set_source_files_properties")
items.append(generateList(keywords.sourceProperties, m_propertyIcon));
if (functionName == "set_target_properties")
items.append(generateList(keywords.targetProperties, m_propertyIcon));
if (functionName == "set_tests_properties")
items.append(generateList(keywords.testProperties, m_propertyIcon));
if (functionName == "include" && !onlyFileItems())
items.append(generateList(keywords.includeStandardModules, m_moduleIcon));
if (functionName == "find_package")
items.append(generateList(keywords.findModules, m_moduleIcon));
if ((functionName.contains("target") || functionName == "install"
|| functionName == "add_dependencies" || functionName == "set_property"
|| functionName == "export")
&& !onlyFileItems())
items.append(generateList(buildTargets, m_targetsIcon));
if (keywords.functionArgs.contains(functionName) && !onlyFileItems()) {
QStringList functionSymbols = keywords.functionArgs.value(functionName);
items.append(generateList(functionSymbols, m_argsIcon));
} else if (functionName.isEmpty()) {
// On a new line we just want functions
items.append(generateList(keywords.functions, m_functionIcon));
items.append(generateList(projectKeywords.functions, m_projectFunctionIcon));
} else {
// Inside an unknown function we could have variables or properties
fileStartPos = addFilePathItems(interface(), items, startPos);
if (!onlyFileItems()) {
items.append(generateList(keywords.variables, m_variableIcon));
items.append(generateList(projectKeywords.variables, m_projectVariableIcon));
items.append(generateList(keywords.properties, m_propertyIcon));
items.append(generateList(buildTargets, m_targetsIcon));
}
}
if (!onlyFileItems())
items.append(m_snippetCollector.collect());
return new GenericProposal(startPos, items);
} }
IAssistProcessor *CMakeFileCompletionAssistProvider::createProcessor(const AssistInterface *) const IAssistProcessor *CMakeFileCompletionAssistProvider::createProcessor(const AssistInterface *) const
@@ -55,4 +322,15 @@ IAssistProcessor *CMakeFileCompletionAssistProvider::createProcessor(const Assis
return new CMakeFileCompletionAssist; return new CMakeFileCompletionAssist;
} }
} // CMakeProjectManager::Internal int CMakeFileCompletionAssistProvider::activationCharSequenceLength() const
{
return 2;
}
bool CMakeFileCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
{
return sequence.endsWith("${") || sequence.endsWith("$<") || sequence.endsWith("/")
|| sequence.endsWith("(");
}
} // namespace CMakeProjectManager::Internal

View File

@@ -3,7 +3,7 @@
#pragma once #pragma once
#include <texteditor/codeassist/keywordscompletionassist.h> #include <texteditor/codeassist/completionassistprovider.h>
namespace CMakeProjectManager::Internal { namespace CMakeProjectManager::Internal {
@@ -11,6 +11,8 @@ class CMakeFileCompletionAssistProvider : public TextEditor::CompletionAssistPro
{ {
public: public:
TextEditor::IAssistProcessor *createProcessor(const TextEditor::AssistInterface *) const final; TextEditor::IAssistProcessor *createProcessor(const TextEditor::AssistInterface *) const final;
int activationCharSequenceLength() const final;
bool isActivationCharSequence(const QString &sequence) const final;
}; };
} // CMakeProjectManager::Internal } // CMakeProjectManager::Internal

View File

@@ -6,6 +6,7 @@
#include "cmakeprojectmanagertr.h" #include "cmakeprojectmanagertr.h"
#include "cmaketoolmanager.h" #include "cmaketoolmanager.h"
#include <coreplugin/icore.h>
#include <coreplugin/helpmanager.h> #include <coreplugin/helpmanager.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
@@ -20,6 +21,7 @@
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QRegularExpression> #include <QRegularExpression>
#include <QSet> #include <QSet>
#include <QXmlStreamReader>
#include <QUuid> #include <QUuid>
#include <memory> #include <memory>
@@ -90,6 +92,14 @@ public:
QVector<FileApi> m_fileApis; QVector<FileApi> m_fileApis;
QStringList m_variables; QStringList m_variables;
QStringList m_functions; QStringList m_functions;
QStringList m_properties;
QStringList m_generatorExpressions;
QStringList m_directoryProperties;
QStringList m_sourceProperties;
QStringList m_targetProperties;
QStringList m_testProperties;
QStringList m_includeStandardModules;
QStringList m_findModules;
CMakeTool::Version m_version; CMakeTool::Version m_version;
}; };
@@ -243,7 +253,7 @@ QList<CMakeTool::Generator> CMakeTool::supportedGenerators() const
return isValid() ? m_introspection->m_generators : QList<CMakeTool::Generator>(); return isValid() ? m_introspection->m_generators : QList<CMakeTool::Generator>();
} }
TextEditor::Keywords CMakeTool::keywords() CMakeKeywords CMakeTool::keywords()
{ {
if (!isValid()) if (!isValid())
return {}; return {};
@@ -252,27 +262,37 @@ TextEditor::Keywords CMakeTool::keywords()
Process proc; Process proc;
runCMake(proc, {"--help-command-list"}, 5); runCMake(proc, {"--help-command-list"}, 5);
if (proc.result() == ProcessResult::FinishedWithSuccess) if (proc.result() == ProcessResult::FinishedWithSuccess)
m_introspection->m_functions = proc.cleanedStdOut().split('\n'); m_introspection->m_functions = Utils::filtered(proc.cleanedStdOut().split('\n'),
std::not_fn(&QString::isEmpty));
runCMake(proc, {"--help-commands"}, 5);
if (proc.result() == ProcessResult::FinishedWithSuccess)
parseFunctionDetailsOutput(proc.cleanedStdOut());
runCMake(proc, {"--help-property-list"}, 5); runCMake(proc, {"--help-property-list"}, 5);
if (proc.result() == ProcessResult::FinishedWithSuccess) if (proc.result() == ProcessResult::FinishedWithSuccess)
m_introspection->m_variables = parseVariableOutput(proc.cleanedStdOut()); m_introspection->m_properties = parseVariableOutput(proc.cleanedStdOut());
runCMake(proc, {"--help-variable-list"}, 5); runCMake(proc, {"--help-variable-list"}, 5);
if (proc.result() == ProcessResult::FinishedWithSuccess) { if (proc.result() == ProcessResult::FinishedWithSuccess) {
m_introspection->m_variables.append(parseVariableOutput(proc.cleanedStdOut())); m_introspection->m_variables = Utils::filteredUnique(
m_introspection->m_variables = Utils::filteredUnique(m_introspection->m_variables); parseVariableOutput(proc.cleanedStdOut()));
Utils::sort(m_introspection->m_variables); Utils::sort(m_introspection->m_variables);
} }
parseSyntaxHighlightingXml();
} }
return TextEditor::Keywords(m_introspection->m_variables, CMakeKeywords keywords;
m_introspection->m_functions, keywords.functions = m_introspection->m_functions;
m_introspection->m_functionArgs); keywords.variables = m_introspection->m_variables;
keywords.functionArgs = m_introspection->m_functionArgs;
keywords.properties = m_introspection->m_properties;
keywords.generatorExpressions = m_introspection->m_generatorExpressions;
keywords.directoryProperties = m_introspection->m_directoryProperties;
keywords.sourceProperties = m_introspection->m_sourceProperties;
keywords.targetProperties = m_introspection->m_targetProperties;
keywords.testProperties = m_introspection->m_testProperties;
keywords.includeStandardModules = m_introspection->m_includeStandardModules;
keywords.findModules = m_introspection->m_findModules;
return keywords;
} }
bool CMakeTool::hasFileApi(bool ignoreCache) const bool CMakeTool::hasFileApi(bool ignoreCache) const
@@ -400,6 +420,7 @@ void CMakeTool::readInformation(bool ignoreCache) const
fetchFromCapabilities(ignoreCache); fetchFromCapabilities(ignoreCache);
} }
static QStringList parseDefinition(const QString &definition) static QStringList parseDefinition(const QString &definition)
{ {
QStringList result; QStringList result;
@@ -455,7 +476,7 @@ void CMakeTool::parseFunctionDetailsOutput(const QString &output)
const QString command = words.takeFirst(); const QString command = words.takeFirst();
if (functionSet.contains(command)) { if (functionSet.contains(command)) {
const QStringList tmp = Utils::sorted( const QStringList tmp = Utils::sorted(
words + m_introspection->m_functionArgs[command]); words + m_introspection->m_functionArgs[command]);
m_introspection->m_functionArgs[command] = Utils::filteredUnique(tmp); m_introspection->m_functionArgs[command] = Utils::filteredUnique(tmp);
} }
} }
@@ -471,12 +492,19 @@ void CMakeTool::parseFunctionDetailsOutput(const QString &output)
QStringList CMakeTool::parseVariableOutput(const QString &output) QStringList CMakeTool::parseVariableOutput(const QString &output)
{ {
const QStringList variableList = output.split('\n'); const QStringList variableList = Utils::filtered(output.split('\n'),
std::not_fn(&QString::isEmpty));
QStringList result; QStringList result;
for (const QString &v : variableList) { for (const QString &v : variableList) {
if (v.startsWith("CMAKE_COMPILER_IS_GNU<LANG>")) { // This key takes a compiler name :-/ if (v.startsWith("CMAKE_COMPILER_IS_GNU<LANG>")) { // This key takes a compiler name :-/
result << "CMAKE_COMPILER_IS_GNUCC" result << "CMAKE_COMPILER_IS_GNUCC"
<< "CMAKE_COMPILER_IS_GNUCXX"; << "CMAKE_COMPILER_IS_GNUCXX";
} else if (v.contains("<CONFIG>") && v.contains("<LANG>")) {
const QString tmp = QString(v).replace("<CONFIG>", "%1").replace("<LANG>", "%2");
result << tmp.arg("DEBUG").arg("C") << tmp.arg("DEBUG").arg("CXX")
<< tmp.arg("RELEASE").arg("C") << tmp.arg("RELEASE").arg("CXX")
<< tmp.arg("MINSIZEREL").arg("C") << tmp.arg("MINSIZEREL").arg("CXX")
<< tmp.arg("RELWITHDEBINFO").arg("C") << tmp.arg("RELWITHDEBINFO").arg("CXX");
} else if (v.contains("<CONFIG>")) { } else if (v.contains("<CONFIG>")) {
const QString tmp = QString(v).replace("<CONFIG>", "%1"); const QString tmp = QString(v).replace("<CONFIG>", "%1");
result << tmp.arg("DEBUG") << tmp.arg("RELEASE") << tmp.arg("MINSIZEREL") result << tmp.arg("DEBUG") << tmp.arg("RELEASE") << tmp.arg("MINSIZEREL")
@@ -491,6 +519,87 @@ QStringList CMakeTool::parseVariableOutput(const QString &output)
return result; return result;
} }
void CMakeTool::parseSyntaxHighlightingXml()
{
QSet<QString> functionSet = Utils::toSet(m_introspection->m_functions);
const FilePath cmakeXml = Core::ICore::resourcePath("generic-highlighter/syntax/cmake.xml");
QXmlStreamReader reader(cmakeXml.fileContents().value_or(QByteArray()));
auto readItemList = [](QXmlStreamReader &reader) -> QStringList {
QStringList arguments;
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"item")
arguments.append(reader.readElementText());
else
reader.skipCurrentElement();
}
return arguments;
};
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() != u"highlighting")
continue;
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"list") {
const auto name = reader.attributes().value("name").toString();
if (name.endsWith(u"_sargs") || name.endsWith(u"_nargs")) {
const auto functionName = name.left(name.length() - 6);
QStringList arguments = readItemList(reader);
if (m_introspection->m_functionArgs.contains(functionName))
arguments.append(m_introspection->m_functionArgs.value(functionName));
m_introspection->m_functionArgs[functionName] = arguments;
// Functions that are part of CMake modules like ExternalProject_Add
// which are not reported by cmake --help-list-commands
if (!functionSet.contains(functionName)) {
functionSet.insert(functionName);
m_introspection->m_functions.append(functionName);
}
} else if (name == u"generator-expressions") {
m_introspection->m_generatorExpressions = readItemList(reader);
} else if (name == u"directory-properties") {
m_introspection->m_directoryProperties = readItemList(reader);
} else if (name == u"source-properties") {
m_introspection->m_sourceProperties = readItemList(reader);
} else if (name == u"target-properties") {
m_introspection->m_targetProperties = readItemList(reader);
} else if (name == u"test-properties") {
m_introspection->m_testProperties = readItemList(reader);
} else if (name == u"standard-modules") {
m_introspection->m_includeStandardModules = readItemList(reader);
} else if (name == u"standard-finder-modules") {
m_introspection->m_findModules = readItemList(reader);
} else {
reader.skipCurrentElement();
}
} else {
reader.skipCurrentElement();
}
}
}
// Some commands have the same arguments as other commands and the `cmake.xml`
// but their relationship is weirdly defined in the `cmake.xml` file.
using ListStringPair = QList<QPair<QString, QString>>;
const ListStringPair functionPairs = {{"if", "elseif"},
{"while", "elseif"},
{"find_path", "find_file"},
{"find_program", "find_library"},
{"target_link_libraries", "target_compile_definitions"},
{"target_link_options", "target_compile_definitions"},
{"target_link_directories", "target_compile_options"},
{"set_target_properties", "set_directory_properties"},
{"set_tests_properties", "set_directory_properties"}};
for (const auto &pair : std::as_const(functionPairs)) {
if (!m_introspection->m_functionArgs.contains(pair.first))
m_introspection->m_functionArgs[pair.first] = m_introspection->m_functionArgs.value(
pair.second);
}
}
void CMakeTool::fetchFromCapabilities(bool ignoreCache) const void CMakeTool::fetchFromCapabilities(bool ignoreCache) const
{ {
expected_str<Utils::Store> cache = PersistentCacheStore::byKey( expected_str<Utils::Store> cache = PersistentCacheStore::byKey(

View File

@@ -19,6 +19,21 @@ namespace CMakeProjectManager {
namespace Internal { class IntrospectionData; } namespace Internal { class IntrospectionData; }
struct CMAKE_EXPORT CMakeKeywords
{
QStringList variables;
QStringList functions;
QStringList properties;
QStringList generatorExpressions;
QStringList directoryProperties;
QStringList sourceProperties;
QStringList targetProperties;
QStringList testProperties;
QStringList includeStandardModules;
QStringList findModules;
QMap<QString, QStringList> functionArgs;
};
class CMAKE_EXPORT CMakeTool class CMAKE_EXPORT CMakeTool
{ {
public: public:
@@ -73,7 +88,7 @@ public:
bool isAutoRun() const; bool isAutoRun() const;
bool autoCreateBuildDirectory() const; bool autoCreateBuildDirectory() const;
QList<Generator> supportedGenerators() const; QList<Generator> supportedGenerators() const;
TextEditor::Keywords keywords(); CMakeKeywords keywords();
bool hasFileApi(bool ignoreCache = false) const; bool hasFileApi(bool ignoreCache = false) const;
Version version() const; Version version() const;
QString versionDisplay() const; QString versionDisplay() const;
@@ -101,6 +116,7 @@ private:
void runCMake(Utils::Process &proc, const QStringList &args, int timeoutS = 1) const; void runCMake(Utils::Process &proc, const QStringList &args, int timeoutS = 1) const;
void parseFunctionDetailsOutput(const QString &output); void parseFunctionDetailsOutput(const QString &output);
QStringList parseVariableOutput(const QString &output); QStringList parseVariableOutput(const QString &output);
void parseSyntaxHighlightingXml();
void fetchFromCapabilities(bool ignoreCache = false) const; void fetchFromCapabilities(bool ignoreCache = false) const;
void parseFromCapabilities(const QString &input) const; void parseFromCapabilities(const QString &input) const;