From 45a8dc4e443dfbc9a7af11a1e2ae6d532ffe9ed4 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Fri, 22 Sep 2023 12:29:25 +0200 Subject: [PATCH] CMakePM: Support local functions and variables for code completion ... and also navigation via F2. Change-Id: I0f1242c6ff502973de180643b369c635636b0112 Reviewed-by: Alessandro Portale --- .../cmakeprojectmanager/cmakeeditor.cpp | 66 +++++++++++++++++-- .../cmakefilecompletionassist.cpp | 33 ++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp index d4e89a26a92..6ba35180c31 100644 --- a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp @@ -11,6 +11,8 @@ #include "cmakekitaspect.h" #include "cmakeprojectconstants.h" +#include "3rdparty/cmake/cmListFileCache.h" + #include #include #include @@ -146,6 +148,35 @@ static QString unescape(const QString &s) return result; } +QHash 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 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(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); } diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp index 1a1f9d57367..5319eb37509 100644 --- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp +++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp @@ -11,6 +11,8 @@ #include "cmaketool.h" #include "cmaketoolmanager.h" +#include "3rdparty/cmake/cmListFileCache.h" + #include #include #include @@ -191,6 +193,30 @@ static int addFilePathItems(const AssistInterface *interface, return startPos; } +QPair 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 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)); }