forked from qt-creator/qt-creator
CMakePM: Support local functions and variables for code completion
... and also navigation via F2. Change-Id: I0f1242c6ff502973de180643b369c635636b0112 Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
#include "cmakekitaspect.h"
|
||||
#include "cmakeprojectconstants.h"
|
||||
|
||||
#include "3rdparty/cmake/cmListFileCache.h"
|
||||
|
||||
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||
#include <coreplugin/actionmanager/actionmanager.h>
|
||||
#include <coreplugin/coreplugintr.h>
|
||||
@@ -146,6 +148,35 @@ static QString unescape(const QString &s)
|
||||
return result;
|
||||
}
|
||||
|
||||
QHash<QString, Utils::Link> getLocalSymbolsHash(const QByteArray &content, const Utils::FilePath &filePath)
|
||||
{
|
||||
cmListFile cmakeListFile;
|
||||
if (!content.isEmpty()) {
|
||||
std::string errorString;
|
||||
const std::string fileName = "buffer";
|
||||
if (!cmakeListFile.ParseString(content.toStdString(), fileName, errorString))
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<QString, Utils::Link> hash;
|
||||
for (const auto &func : cmakeListFile.Functions) {
|
||||
if (func.LowerCaseName() != "function" && func.LowerCaseName() != "macro"
|
||||
&& func.LowerCaseName() != "set" && func.LowerCaseName() != "option")
|
||||
continue;
|
||||
|
||||
if (func.Arguments().size() == 0)
|
||||
continue;
|
||||
auto arg = func.Arguments()[0];
|
||||
|
||||
Utils::Link link;
|
||||
link.targetFilePath = filePath;
|
||||
link.targetLine = arg.Line;
|
||||
link.targetColumn = arg.Column - 1;
|
||||
hash.insert(QString::fromUtf8(arg.Value), link);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
void CMakeEditorWidget::findLinkAt(const QTextCursor &cursor,
|
||||
const Utils::LinkHandler &processLinkCallback,
|
||||
bool/* resolveTarget*/,
|
||||
@@ -194,6 +225,12 @@ void CMakeEditorWidget::findLinkAt(const QTextCursor &cursor,
|
||||
buffer.replace("${CMAKE_CURRENT_SOURCE_DIR}", dir.path());
|
||||
buffer.replace("${CMAKE_CURRENT_LIST_DIR}", dir.path());
|
||||
|
||||
auto addTextStartEndToLink = [&](Utils::Link &link) {
|
||||
link.linkTextStart = cursor.position() - column + beginPos + 1;
|
||||
link.linkTextEnd = cursor.position() - column + endPos;
|
||||
return link;
|
||||
};
|
||||
|
||||
if (auto project = ProjectTree::currentProject()) {
|
||||
buffer.replace("${CMAKE_SOURCE_DIR}", project->projectDirectory().path());
|
||||
if (auto bs = ProjectTree::currentBuildSystem()) {
|
||||
@@ -216,14 +253,35 @@ void CMakeEditorWidget::findLinkAt(const QTextCursor &cursor,
|
||||
const CMakeBuildSystem *cbs = static_cast<const CMakeBuildSystem *>(bs);
|
||||
if (cbs->cmakeSymbolsHash().contains(buffer)) {
|
||||
link = cbs->cmakeSymbolsHash().value(buffer);
|
||||
link.linkTextStart = cursor.position() - column + beginPos + 1;
|
||||
link.linkTextEnd = cursor.position() - column + endPos;
|
||||
addTextStartEndToLink(link);
|
||||
return processLinkCallback(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Resolve more variables
|
||||
|
||||
// Resolve local variables and functions
|
||||
auto findFunctionEnd = [cursor, this]() -> int {
|
||||
int pos = cursor.position();
|
||||
QChar chr;
|
||||
do {
|
||||
chr = textDocument()->characterAt(--pos);
|
||||
} while (pos > 0 && chr != ')');
|
||||
return pos;
|
||||
};
|
||||
auto hash = getLocalSymbolsHash(textDocument()->textAt(0, findFunctionEnd() + 1).toUtf8(),
|
||||
textDocument()->filePath());
|
||||
|
||||
// Strip variable coating
|
||||
if (buffer.startsWith("${") && buffer.endsWith("}"))
|
||||
buffer = buffer.mid(2, buffer.size() - 3);
|
||||
|
||||
if (hash.contains(buffer)) {
|
||||
link = hash.value(buffer);
|
||||
addTextStartEndToLink(link);
|
||||
return processLinkCallback(link);
|
||||
}
|
||||
|
||||
Utils::FilePath fileName = dir.withNewPath(unescape(buffer));
|
||||
if (fileName.isRelativePath())
|
||||
fileName = dir.pathAppended(fileName.path());
|
||||
@@ -236,9 +294,9 @@ void CMakeEditorWidget::findLinkAt(const QTextCursor &cursor,
|
||||
return processLinkCallback(link);
|
||||
}
|
||||
link.targetFilePath = fileName;
|
||||
link.linkTextStart = cursor.position() - column + beginPos + 1;
|
||||
link.linkTextEnd = cursor.position() - column + endPos;
|
||||
addTextStartEndToLink(link);
|
||||
}
|
||||
|
||||
processLinkCallback(link);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "cmaketool.h"
|
||||
#include "cmaketoolmanager.h"
|
||||
|
||||
#include "3rdparty/cmake/cmListFileCache.h"
|
||||
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
#include <projectexplorer/projectexplorericons.h>
|
||||
@@ -191,6 +193,30 @@ static int addFilePathItems(const AssistInterface *interface,
|
||||
return startPos;
|
||||
}
|
||||
|
||||
QPair<QStringList, QStringList> getLocalFunctionsAndVariables(const QByteArray &content)
|
||||
{
|
||||
cmListFile cmakeListFile;
|
||||
std::string errorString;
|
||||
if (!content.isEmpty()) {
|
||||
const std::string fileName = "buffer";
|
||||
if (!cmakeListFile.ParseString(content.toStdString(), fileName, errorString))
|
||||
return {{}, {}};
|
||||
}
|
||||
|
||||
QStringList variables;
|
||||
QStringList functions;
|
||||
for (const auto &func : cmakeListFile.Functions) {
|
||||
if (func.Arguments().size() == 0)
|
||||
continue;
|
||||
|
||||
if (func.LowerCaseName() == "macro" || func.LowerCaseName() == "function")
|
||||
functions << QString::fromUtf8(func.Arguments()[0].Value);
|
||||
if (func.LowerCaseName() == "set" || func.LowerCaseName() == "option")
|
||||
variables << QString::fromUtf8(func.Arguments()[0].Value);
|
||||
}
|
||||
return {functions, variables};
|
||||
}
|
||||
|
||||
IAssistProposal *CMakeFileCompletionAssist::performAsync()
|
||||
{
|
||||
CMakeKeywords keywords;
|
||||
@@ -245,6 +271,9 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync()
|
||||
}
|
||||
}
|
||||
|
||||
auto [localFunctions, localVariables] = getLocalFunctionsAndVariables(
|
||||
interface()->textAt(0, prevFunctionEnd + 1).toUtf8());
|
||||
|
||||
QList<AssistProposalItemInterface *> items;
|
||||
|
||||
const QString varGenexToken = interface()->textAt(startPos - 2, 2);
|
||||
@@ -267,6 +296,7 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync()
|
||||
|| functionName == "cmake_print_variables") {
|
||||
items.append(generateList(keywords.variables, m_variableIcon));
|
||||
items.append(generateList(projectKeywords.variables, m_projectVariableIcon));
|
||||
items.append(generateList(localVariables, m_variableIcon));
|
||||
}
|
||||
|
||||
if (functionName == "if" || functionName == "elseif" || functionName == "cmake_policy")
|
||||
@@ -310,6 +340,7 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync()
|
||||
// On a new line we just want functions
|
||||
items.append(generateList(keywords.functions, m_functionIcon));
|
||||
items.append(generateList(projectKeywords.functions, m_projectFunctionIcon));
|
||||
items.append(generateList(localFunctions, m_functionIcon));
|
||||
|
||||
// Snippets would make more sense only for the top level suggestions
|
||||
items.append(m_snippetCollector.collect());
|
||||
@@ -319,6 +350,8 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync()
|
||||
if (!onlyFileItems()) {
|
||||
items.append(generateList(keywords.variables, m_variableIcon));
|
||||
items.append(generateList(projectKeywords.variables, m_projectVariableIcon));
|
||||
items.append(generateList(localVariables, m_variableIcon));
|
||||
|
||||
items.append(generateList(keywords.properties, m_propertyIcon));
|
||||
items.append(generateList(buildTargets, m_targetsIcon));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user