forked from qt-creator/qt-creator
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:
@@ -1250,6 +1250,9 @@ void CMakeBuildSystem::setupCMakeSymbolsHash()
|
||||
{
|
||||
m_cmakeSymbolsHash.clear();
|
||||
|
||||
m_projectKeywords.functions.clear();
|
||||
m_projectKeywords.variables.clear();
|
||||
|
||||
for (const auto &cmakeFile : std::as_const(m_cmakeFiles)) {
|
||||
for (const auto &func : cmakeFile.cmakeListFile.Functions) {
|
||||
if (func.LowerCaseName() != "function" && func.LowerCaseName() != "macro"
|
||||
@@ -1265,8 +1268,17 @@ void CMakeBuildSystem::setupCMakeSymbolsHash()
|
||||
link.targetLine = arg.Line;
|
||||
link.targetColumn = arg.Column - 1;
|
||||
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 ¶meters)
|
||||
|
||||
@@ -119,6 +119,7 @@ public:
|
||||
QString warning() const;
|
||||
|
||||
const QHash<QString, Utils::Link> &cmakeSymbolsHash() const { return m_cmakeSymbolsHash; }
|
||||
CMakeKeywords projectKeywords() const { return m_projectKeywords; }
|
||||
|
||||
signals:
|
||||
void configurationCleared();
|
||||
@@ -223,6 +224,7 @@ private:
|
||||
QList<CMakeBuildTarget> m_buildTargets;
|
||||
QSet<CMakeFileInfo> m_cmakeFiles;
|
||||
QHash<QString, Utils::Link> m_cmakeSymbolsHash;
|
||||
CMakeKeywords m_projectKeywords;
|
||||
|
||||
QHash<QString, ProjectFileArgumentPosition> m_filesToBeRenamed;
|
||||
|
||||
|
||||
@@ -3,51 +3,318 @@
|
||||
|
||||
#include "cmakefilecompletionassist.h"
|
||||
|
||||
#include "cmakebuildsystem.h"
|
||||
#include "cmakebuildtarget.h"
|
||||
#include "cmakekitaspect.h"
|
||||
#include "cmakeproject.h"
|
||||
#include "cmakeprojectconstants.h"
|
||||
#include "cmaketool.h"
|
||||
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
#include <projectexplorer/projectexplorericons.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/projectnodes.h>
|
||||
#include <projectexplorer/target.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 ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
namespace CMakeProjectManager::Internal {
|
||||
|
||||
class CMakeFileCompletionAssist : public KeywordsCompletionAssistProcessor
|
||||
class CMakeFileCompletionAssist : public AsyncProcessor
|
||||
{
|
||||
public:
|
||||
CMakeFileCompletionAssist();
|
||||
|
||||
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() :
|
||||
KeywordsCompletionAssistProcessor(Keywords())
|
||||
CMakeFileCompletionAssist::CMakeFileCompletionAssist()
|
||||
: 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);
|
||||
setDynamicCompletionFunction(&TextEditor::pathComplete);
|
||||
QTextCursor tc(interface->textDocument());
|
||||
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()
|
||||
{
|
||||
Keywords kw;
|
||||
const Utils::FilePath &filePath = interface()->filePath();
|
||||
CMakeKeywords keywords;
|
||||
CMakeKeywords projectKeywords;
|
||||
Project *project = nullptr;
|
||||
const FilePath &filePath = interface()->filePath();
|
||||
if (!filePath.isEmpty() && filePath.isFile()) {
|
||||
Project *p = ProjectManager::projectForFile(filePath);
|
||||
if (p && p->activeTarget()) {
|
||||
CMakeTool *cmake = CMakeKitAspect::cmakeTool(p->activeTarget()->kit());
|
||||
project = static_cast<CMakeProject *>(ProjectManager::projectForFile(filePath));
|
||||
if (project && project->activeTarget()) {
|
||||
CMakeTool *cmake = CMakeKitAspect::cmakeTool(project->activeTarget()->kit());
|
||||
if (cmake && cmake->isValid())
|
||||
kw = cmake->keywords();
|
||||
keywords = cmake->keywords();
|
||||
}
|
||||
}
|
||||
|
||||
setKeywords(kw);
|
||||
return KeywordsCompletionAssistProcessor::performAsync();
|
||||
QStringList buildTargets;
|
||||
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
|
||||
@@ -55,4 +322,15 @@ IAssistProcessor *CMakeFileCompletionAssistProvider::createProcessor(const Assis
|
||||
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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <texteditor/codeassist/keywordscompletionassist.h>
|
||||
#include <texteditor/codeassist/completionassistprovider.h>
|
||||
|
||||
namespace CMakeProjectManager::Internal {
|
||||
|
||||
@@ -11,6 +11,8 @@ class CMakeFileCompletionAssistProvider : public TextEditor::CompletionAssistPro
|
||||
{
|
||||
public:
|
||||
TextEditor::IAssistProcessor *createProcessor(const TextEditor::AssistInterface *) const final;
|
||||
int activationCharSequenceLength() const final;
|
||||
bool isActivationCharSequence(const QString &sequence) const final;
|
||||
};
|
||||
|
||||
} // CMakeProjectManager::Internal
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "cmakeprojectmanagertr.h"
|
||||
#include "cmaketoolmanager.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/helpmanager.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
@@ -20,6 +21,7 @@
|
||||
#include <QLoggingCategory>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QUuid>
|
||||
|
||||
#include <memory>
|
||||
@@ -90,6 +92,14 @@ public:
|
||||
QVector<FileApi> m_fileApis;
|
||||
QStringList m_variables;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -243,7 +253,7 @@ QList<CMakeTool::Generator> CMakeTool::supportedGenerators() const
|
||||
return isValid() ? m_introspection->m_generators : QList<CMakeTool::Generator>();
|
||||
}
|
||||
|
||||
TextEditor::Keywords CMakeTool::keywords()
|
||||
CMakeKeywords CMakeTool::keywords()
|
||||
{
|
||||
if (!isValid())
|
||||
return {};
|
||||
@@ -252,27 +262,37 @@ TextEditor::Keywords CMakeTool::keywords()
|
||||
Process proc;
|
||||
runCMake(proc, {"--help-command-list"}, 5);
|
||||
if (proc.result() == ProcessResult::FinishedWithSuccess)
|
||||
m_introspection->m_functions = proc.cleanedStdOut().split('\n');
|
||||
|
||||
runCMake(proc, {"--help-commands"}, 5);
|
||||
if (proc.result() == ProcessResult::FinishedWithSuccess)
|
||||
parseFunctionDetailsOutput(proc.cleanedStdOut());
|
||||
m_introspection->m_functions = Utils::filtered(proc.cleanedStdOut().split('\n'),
|
||||
std::not_fn(&QString::isEmpty));
|
||||
|
||||
runCMake(proc, {"--help-property-list"}, 5);
|
||||
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);
|
||||
if (proc.result() == ProcessResult::FinishedWithSuccess) {
|
||||
m_introspection->m_variables.append(parseVariableOutput(proc.cleanedStdOut()));
|
||||
m_introspection->m_variables = Utils::filteredUnique(m_introspection->m_variables);
|
||||
m_introspection->m_variables = Utils::filteredUnique(
|
||||
parseVariableOutput(proc.cleanedStdOut()));
|
||||
Utils::sort(m_introspection->m_variables);
|
||||
}
|
||||
|
||||
parseSyntaxHighlightingXml();
|
||||
}
|
||||
|
||||
return TextEditor::Keywords(m_introspection->m_variables,
|
||||
m_introspection->m_functions,
|
||||
m_introspection->m_functionArgs);
|
||||
CMakeKeywords keywords;
|
||||
keywords.functions = m_introspection->m_functions;
|
||||
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
|
||||
@@ -400,6 +420,7 @@ void CMakeTool::readInformation(bool ignoreCache) const
|
||||
fetchFromCapabilities(ignoreCache);
|
||||
}
|
||||
|
||||
|
||||
static QStringList parseDefinition(const QString &definition)
|
||||
{
|
||||
QStringList result;
|
||||
@@ -455,7 +476,7 @@ void CMakeTool::parseFunctionDetailsOutput(const QString &output)
|
||||
const QString command = words.takeFirst();
|
||||
if (functionSet.contains(command)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -471,12 +492,19 @@ void CMakeTool::parseFunctionDetailsOutput(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;
|
||||
for (const QString &v : variableList) {
|
||||
if (v.startsWith("CMAKE_COMPILER_IS_GNU<LANG>")) { // This key takes a compiler name :-/
|
||||
result << "CMAKE_COMPILER_IS_GNUCC"
|
||||
<< "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>")) {
|
||||
const QString tmp = QString(v).replace("<CONFIG>", "%1");
|
||||
result << tmp.arg("DEBUG") << tmp.arg("RELEASE") << tmp.arg("MINSIZEREL")
|
||||
@@ -491,6 +519,87 @@ QStringList CMakeTool::parseVariableOutput(const QString &output)
|
||||
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
|
||||
{
|
||||
expected_str<Utils::Store> cache = PersistentCacheStore::byKey(
|
||||
|
||||
@@ -19,6 +19,21 @@ namespace CMakeProjectManager {
|
||||
|
||||
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
|
||||
{
|
||||
public:
|
||||
@@ -73,7 +88,7 @@ public:
|
||||
bool isAutoRun() const;
|
||||
bool autoCreateBuildDirectory() const;
|
||||
QList<Generator> supportedGenerators() const;
|
||||
TextEditor::Keywords keywords();
|
||||
CMakeKeywords keywords();
|
||||
bool hasFileApi(bool ignoreCache = false) const;
|
||||
Version version() const;
|
||||
QString versionDisplay() const;
|
||||
@@ -101,6 +116,7 @@ private:
|
||||
void runCMake(Utils::Process &proc, const QStringList &args, int timeoutS = 1) const;
|
||||
void parseFunctionDetailsOutput(const QString &output);
|
||||
QStringList parseVariableOutput(const QString &output);
|
||||
void parseSyntaxHighlightingXml();
|
||||
|
||||
void fetchFromCapabilities(bool ignoreCache = false) const;
|
||||
void parseFromCapabilities(const QString &input) const;
|
||||
|
||||
Reference in New Issue
Block a user