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_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 ¶meters)
|
void CMakeBuildSystem::ensureBuildDirectory(const BuildDirParameters ¶meters)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user