From 0c60370f3e3ab79b90a4530d104ed50606d09dce Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 10 Nov 2021 12:35:06 +0100 Subject: [PATCH] ClangCodeModel: Fix Qt header completion with clangd < 14 The problem is fixed upstream, but this must work now. Fixes: QTCREATORBUG-26482 Change-Id: I3b2e863efec0edf7eaa74d73eb94705aa28723cf Reviewed-by: David Schulz Reviewed-by: Qt CI Bot --- .../clangcompletionassistprocessor.cpp | 62 ++++++++++--------- .../clangcompletionassistprocessor.h | 16 +++-- src/plugins/clangcodemodel/clangdclient.cpp | 34 ++++++++-- .../clangcodemodel/test/clangdtests.cpp | 12 +++- 4 files changed, 85 insertions(+), 39 deletions(-) diff --git a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp index 1d924f8b2da..ac8f686910a 100644 --- a/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp +++ b/src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp @@ -338,7 +338,9 @@ IAssistProposal *ClangCompletionAssistProcessor::startCompletionHelper() return createProposal(); break; case ClangCompletionContextAnalyzer::CompleteIncludePath: - if (completeInclude(analyzer.positionEndOfExpression())) + m_completions = completeInclude(analyzer.positionEndOfExpression(), m_completionOperator, + m_interface.data(), m_interface->headerPaths()); + if (!m_completions.isEmpty()) return createProposal(); break; case ClangCompletionContextAnalyzer::CompletePreprocessorDirective: @@ -441,38 +443,46 @@ bool ClangCompletionAssistProcessor::accepts() const /** * @brief Creates completion proposals for #include and given cursor - * @param cursor - cursor placed after opening bracked or quote - * @return false if completions list is empty + * @param position - cursor placed after opening bracked or quote + * @param completionOperator - the type of token + * @param interface - relevant document data + * @param headerPaths - the include paths + * @return the list of completion items */ -bool ClangCompletionAssistProcessor::completeInclude(const QTextCursor &cursor) +QList ClangCompletionAssistProcessor::completeInclude( + int position, unsigned completionOperator, const TextEditor::AssistInterface *interface, + const ProjectExplorer::HeaderPaths &headerPaths) { + QTextCursor cursor(interface->textDocument()); + cursor.setPosition(position); QString directoryPrefix; - if (m_completionOperator == T_SLASH) { + if (completionOperator == T_SLASH) { QTextCursor c = cursor; c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); QString sel = c.selectedText(); int startCharPos = sel.indexOf(QLatin1Char('"')); if (startCharPos == -1) { startCharPos = sel.indexOf(QLatin1Char('<')); - m_completionOperator = T_ANGLE_STRING_LITERAL; + completionOperator = T_ANGLE_STRING_LITERAL; } else { - m_completionOperator = T_STRING_LITERAL; + completionOperator = T_STRING_LITERAL; } if (startCharPos != -1) directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1); } // Make completion for all relevant includes - ProjectExplorer::HeaderPaths headerPaths = m_interface->headerPaths(); + ProjectExplorer::HeaderPaths allHeaderPaths = headerPaths; const auto currentFilePath = ProjectExplorer::HeaderPath::makeUser( - m_interface->filePath().toFileInfo().path()); - if (!headerPaths.contains(currentFilePath)) - headerPaths.append(currentFilePath); + interface->filePath().toFileInfo().path()); + if (!allHeaderPaths.contains(currentFilePath)) + allHeaderPaths.append(currentFilePath); const ::Utils::MimeType mimeType = ::Utils::mimeTypeForName("text/x-c++hdr"); const QStringList suffixes = mimeType.suffixes(); - foreach (const ProjectExplorer::HeaderPath &headerPath, headerPaths) { + QList completions; + foreach (const ProjectExplorer::HeaderPath &headerPath, allHeaderPaths) { QString realPath = headerPath.path; if (!directoryPrefix.isEmpty()) { realPath += QLatin1Char('/'); @@ -480,11 +490,11 @@ bool ClangCompletionAssistProcessor::completeInclude(const QTextCursor &cursor) if (headerPath.type == ProjectExplorer::HeaderPathType::Framework) realPath += QLatin1String(".framework/Headers"); } - completeIncludePath(realPath, suffixes); + completions << completeIncludePath(realPath, suffixes, completionOperator); } QList> completionsForSorting; - for (AssistProposalItemInterface * const item : qAsConst(m_completions)) { + for (AssistProposalItemInterface * const item : qAsConst(completions)) { QString s = item->text(); s.replace('/', QChar(0)); // The dir separator should compare less than anything else. completionsForSorting << qMakePair(item, s); @@ -493,26 +503,21 @@ bool ClangCompletionAssistProcessor::completeInclude(const QTextCursor &cursor) return left.second < right.second; }); for (int i = 0; i < completionsForSorting.count(); ++i) - m_completions[i] = completionsForSorting[i].first; + completions[i] = completionsForSorting[i].first; - return !m_completions.isEmpty(); -} - -bool ClangCompletionAssistProcessor::completeInclude(int position) -{ - QTextCursor textCursor(m_interface->textDocument()); // TODO: Simplify, move into function - textCursor.setPosition(position); - return completeInclude(textCursor); + return completions; } /** - * @brief Adds #include completion proposals using given include path + * @brief Finds #include completion proposals using given include path * @param realPath - one of directories where compiler searches includes * @param suffixes - file suffixes for C/C++ header files + * @return a list of matching completion items */ -void ClangCompletionAssistProcessor::completeIncludePath(const QString &realPath, - const QStringList &suffixes) +QList ClangCompletionAssistProcessor::completeIncludePath( + const QString &realPath, const QStringList &suffixes, unsigned completionOperator) { + QList completions; QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); //: Parent folder for proposed #include completion const QString hint = tr("Location: %1").arg(QDir::toNativeSeparators(QDir::cleanPath(realPath))); @@ -529,10 +534,11 @@ void ClangCompletionAssistProcessor::completeIncludePath(const QString &realPath item->setText(text); item->setDetail(hint); item->setIcon(CPlusPlus::Icons::keywordIcon()); - item->setCompletionOperator(m_completionOperator); - m_completions.append(item); + item->setCompletionOperator(completionOperator); + completions.append(item); } } + return completions; } bool ClangCompletionAssistProcessor::completePreprocessorDirectives() diff --git a/src/plugins/clangcodemodel/clangcompletionassistprocessor.h b/src/plugins/clangcodemodel/clangcompletionassistprocessor.h index 78826f598a8..1d67344cc86 100644 --- a/src/plugins/clangcodemodel/clangcompletionassistprocessor.h +++ b/src/plugins/clangcodemodel/clangcompletionassistprocessor.h @@ -34,6 +34,11 @@ #include #include +namespace TextEditor { +class AssistInterface; +class AssistProposalItemInterface; +} + namespace ClangCodeModel { namespace Internal { @@ -48,6 +53,11 @@ public: ClangCompletionAssistProcessor(); ~ClangCompletionAssistProcessor() override; + static QList completeInclude( + int position, unsigned completionOperator, + const TextEditor::AssistInterface *interface, + const ProjectExplorer::HeaderPaths &headerPaths); + TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; void handleAvailableCompletions(const CodeCompletions &completions); @@ -65,12 +75,10 @@ private: TextEditor::IAssistProposal *createProposal(); TextEditor::IAssistProposal *createFunctionHintProposal( const CodeCompletions &completions); - QList toAssistProposalItems( const CodeCompletions &completions) const; - bool completeInclude(const QTextCursor &cursor); - bool completeInclude(int position); - void completeIncludePath(const QString &realPath, const QStringList &suffixes); + static QList completeIncludePath( + const QString &realPath, const QStringList &suffixes, unsigned completionOperator); bool completePreprocessorDirectives(); bool completeDoxygenKeywords(); void addCompletionItem(const QString &text, diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index be8dbfda9a3..32996f6d015 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -25,6 +25,7 @@ #include "clangdclient.h" +#include "clangcompletionassistprocessor.h" #include "clangcompletioncontextanalyzer.h" #include "clangdiagnosticmanager.h" #include "clangmodelmanagersupport.h" @@ -811,14 +812,15 @@ public: }; -enum class CustomAssistMode { Doxygen, Preprocessor }; +enum class CustomAssistMode { Doxygen, Preprocessor, IncludePath }; class CustomAssistProcessor : public IAssistProcessor { public: - CustomAssistProcessor(ClangdClient *client, int position, unsigned completionOperator, - CustomAssistMode mode) + CustomAssistProcessor(ClangdClient *client, int position, int endPos, + unsigned completionOperator, CustomAssistMode mode) : m_client(client) , m_position(position) + , m_endPos(endPos) , m_completionOperator(completionOperator) , m_mode(mode) {} @@ -834,7 +836,7 @@ private: CPlusPlus::Icons::keywordIcon()); } break; - case CustomAssistMode::Preprocessor: + case CustomAssistMode::Preprocessor: { static QIcon macroIcon = Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Macro); for (const QString &completion : CppEditor::CppCompletionAssistProcessor::preprocessorCompletions()) { @@ -844,6 +846,17 @@ private: completions << createItem("import", macroIcon); break; } + case ClangCodeModel::Internal::CustomAssistMode::IncludePath: { + HeaderPaths headerPaths; + const CppEditor::ProjectPart::ConstPtr projectPart + = projectPartForFile(interface->filePath().toString()); + if (projectPart) + headerPaths = projectPart->headerPaths; + completions = ClangCompletionAssistProcessor::completeInclude( + m_endPos, m_completionOperator, interface, headerPaths); + break; + } + } GenericProposalModelPtr model(new GenericProposalModel); model->loadContent(completions); const auto proposal = new GenericProposal(m_position, model); @@ -865,6 +878,7 @@ private: ClangdClient * const m_client; const int m_position; + const int m_endPos; const unsigned m_completionOperator; const CustomAssistMode m_mode; }; @@ -2816,14 +2830,26 @@ IAssistProcessor *ClangdClient::ClangdCompletionAssistProvider::createProcessor( qCDebug(clangdLogCompletion) << "creating doxygen processor"; return new CustomAssistProcessor(m_client, contextAnalyzer.positionForProposal(), + contextAnalyzer.positionEndOfExpression(), contextAnalyzer.completionOperator(), CustomAssistMode::Doxygen); case ClangCompletionContextAnalyzer::CompletePreprocessorDirective: qCDebug(clangdLogCompletion) << "creating macro processor"; return new CustomAssistProcessor(m_client, contextAnalyzer.positionForProposal(), + contextAnalyzer.positionEndOfExpression(), contextAnalyzer.completionOperator(), CustomAssistMode::Preprocessor); + case ClangCompletionContextAnalyzer::CompleteIncludePath: + if (m_client->versionNumber() < QVersionNumber(14)) { // https://reviews.llvm.org/D112996 + qCDebug(clangdLogCompletion) << "creating include processor"; + return new CustomAssistProcessor(m_client, + contextAnalyzer.positionForProposal(), + contextAnalyzer.positionEndOfExpression(), + contextAnalyzer.completionOperator(), + CustomAssistMode::IncludePath); + } + [[fallthrough]]; default: break; } diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index cc532cfda03..bd1a803d41a 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -1488,9 +1488,15 @@ void ClangdTestCompletion::testCompleteIncludeDirective() getProposal("includeDirectiveCompletion.cpp", proposal); QVERIFY(proposal); - QVERIFY(hasItem(proposal, " file.h>")); - QVERIFY(hasItem(proposal, " otherFile.h>")); - QVERIFY(hasItem(proposal, " mylib/")); + if (client()->versionNumber() < QVersionNumber(14)) { + QVERIFY(hasItem(proposal, "file.h")); + QVERIFY(hasItem(proposal, "otherFile.h")); + QVERIFY(hasItem(proposal, "mylib/")); + } else { + QVERIFY(hasItem(proposal, " file.h>")); + QVERIFY(hasItem(proposal, " otherFile.h>")); + QVERIFY(hasItem(proposal, " mylib/")); + } QVERIFY(!hasSnippet(proposal, "class ")); }