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:
Cristian Adam
2023-09-22 12:29:25 +02:00
parent 66317f0145
commit 45a8dc4e44
2 changed files with 95 additions and 4 deletions

View File

@@ -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);
}

View File

@@ -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));
}