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 <david.schulz@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Christian Kandeler
2021-11-10 12:35:06 +01:00
parent 9d90715752
commit 0c60370f3e
4 changed files with 85 additions and 39 deletions

View File

@@ -338,7 +338,9 @@ IAssistProposal *ClangCompletionAssistProcessor::startCompletionHelper()
return createProposal(); return createProposal();
break; break;
case ClangCompletionContextAnalyzer::CompleteIncludePath: 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(); return createProposal();
break; break;
case ClangCompletionContextAnalyzer::CompletePreprocessorDirective: case ClangCompletionContextAnalyzer::CompletePreprocessorDirective:
@@ -441,38 +443,46 @@ bool ClangCompletionAssistProcessor::accepts() const
/** /**
* @brief Creates completion proposals for #include and given cursor * @brief Creates completion proposals for #include and given cursor
* @param cursor - cursor placed after opening bracked or quote * @param position - cursor placed after opening bracked or quote
* @return false if completions list is empty * @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<AssistProposalItemInterface *> ClangCompletionAssistProcessor::completeInclude(
int position, unsigned completionOperator, const TextEditor::AssistInterface *interface,
const ProjectExplorer::HeaderPaths &headerPaths)
{ {
QTextCursor cursor(interface->textDocument());
cursor.setPosition(position);
QString directoryPrefix; QString directoryPrefix;
if (m_completionOperator == T_SLASH) { if (completionOperator == T_SLASH) {
QTextCursor c = cursor; QTextCursor c = cursor;
c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
QString sel = c.selectedText(); QString sel = c.selectedText();
int startCharPos = sel.indexOf(QLatin1Char('"')); int startCharPos = sel.indexOf(QLatin1Char('"'));
if (startCharPos == -1) { if (startCharPos == -1) {
startCharPos = sel.indexOf(QLatin1Char('<')); startCharPos = sel.indexOf(QLatin1Char('<'));
m_completionOperator = T_ANGLE_STRING_LITERAL; completionOperator = T_ANGLE_STRING_LITERAL;
} else { } else {
m_completionOperator = T_STRING_LITERAL; completionOperator = T_STRING_LITERAL;
} }
if (startCharPos != -1) if (startCharPos != -1)
directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1); directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1);
} }
// Make completion for all relevant includes // Make completion for all relevant includes
ProjectExplorer::HeaderPaths headerPaths = m_interface->headerPaths(); ProjectExplorer::HeaderPaths allHeaderPaths = headerPaths;
const auto currentFilePath = ProjectExplorer::HeaderPath::makeUser( const auto currentFilePath = ProjectExplorer::HeaderPath::makeUser(
m_interface->filePath().toFileInfo().path()); interface->filePath().toFileInfo().path());
if (!headerPaths.contains(currentFilePath)) if (!allHeaderPaths.contains(currentFilePath))
headerPaths.append(currentFilePath); allHeaderPaths.append(currentFilePath);
const ::Utils::MimeType mimeType = ::Utils::mimeTypeForName("text/x-c++hdr"); const ::Utils::MimeType mimeType = ::Utils::mimeTypeForName("text/x-c++hdr");
const QStringList suffixes = mimeType.suffixes(); const QStringList suffixes = mimeType.suffixes();
foreach (const ProjectExplorer::HeaderPath &headerPath, headerPaths) { QList<AssistProposalItemInterface *> completions;
foreach (const ProjectExplorer::HeaderPath &headerPath, allHeaderPaths) {
QString realPath = headerPath.path; QString realPath = headerPath.path;
if (!directoryPrefix.isEmpty()) { if (!directoryPrefix.isEmpty()) {
realPath += QLatin1Char('/'); realPath += QLatin1Char('/');
@@ -480,11 +490,11 @@ bool ClangCompletionAssistProcessor::completeInclude(const QTextCursor &cursor)
if (headerPath.type == ProjectExplorer::HeaderPathType::Framework) if (headerPath.type == ProjectExplorer::HeaderPathType::Framework)
realPath += QLatin1String(".framework/Headers"); realPath += QLatin1String(".framework/Headers");
} }
completeIncludePath(realPath, suffixes); completions << completeIncludePath(realPath, suffixes, completionOperator);
} }
QList<QPair<AssistProposalItemInterface *, QString>> completionsForSorting; QList<QPair<AssistProposalItemInterface *, QString>> completionsForSorting;
for (AssistProposalItemInterface * const item : qAsConst(m_completions)) { for (AssistProposalItemInterface * const item : qAsConst(completions)) {
QString s = item->text(); QString s = item->text();
s.replace('/', QChar(0)); // The dir separator should compare less than anything else. s.replace('/', QChar(0)); // The dir separator should compare less than anything else.
completionsForSorting << qMakePair(item, s); completionsForSorting << qMakePair(item, s);
@@ -493,26 +503,21 @@ bool ClangCompletionAssistProcessor::completeInclude(const QTextCursor &cursor)
return left.second < right.second; return left.second < right.second;
}); });
for (int i = 0; i < completionsForSorting.count(); ++i) for (int i = 0; i < completionsForSorting.count(); ++i)
m_completions[i] = completionsForSorting[i].first; completions[i] = completionsForSorting[i].first;
return !m_completions.isEmpty(); return completions;
}
bool ClangCompletionAssistProcessor::completeInclude(int position)
{
QTextCursor textCursor(m_interface->textDocument()); // TODO: Simplify, move into function
textCursor.setPosition(position);
return completeInclude(textCursor);
} }
/** /**
* @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 realPath - one of directories where compiler searches includes
* @param suffixes - file suffixes for C/C++ header files * @param suffixes - file suffixes for C/C++ header files
* @return a list of matching completion items
*/ */
void ClangCompletionAssistProcessor::completeIncludePath(const QString &realPath, QList<AssistProposalItemInterface *> ClangCompletionAssistProcessor::completeIncludePath(
const QStringList &suffixes) const QString &realPath, const QStringList &suffixes, unsigned completionOperator)
{ {
QList<AssistProposalItemInterface *> completions;
QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
//: Parent folder for proposed #include completion //: Parent folder for proposed #include completion
const QString hint = tr("Location: %1").arg(QDir::toNativeSeparators(QDir::cleanPath(realPath))); 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->setText(text);
item->setDetail(hint); item->setDetail(hint);
item->setIcon(CPlusPlus::Icons::keywordIcon()); item->setIcon(CPlusPlus::Icons::keywordIcon());
item->setCompletionOperator(m_completionOperator); item->setCompletionOperator(completionOperator);
m_completions.append(item); completions.append(item);
} }
} }
return completions;
} }
bool ClangCompletionAssistProcessor::completePreprocessorDirectives() bool ClangCompletionAssistProcessor::completePreprocessorDirectives()

View File

@@ -34,6 +34,11 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QTextCursor> #include <QTextCursor>
namespace TextEditor {
class AssistInterface;
class AssistProposalItemInterface;
}
namespace ClangCodeModel { namespace ClangCodeModel {
namespace Internal { namespace Internal {
@@ -48,6 +53,11 @@ public:
ClangCompletionAssistProcessor(); ClangCompletionAssistProcessor();
~ClangCompletionAssistProcessor() override; ~ClangCompletionAssistProcessor() override;
static QList<TextEditor::AssistProposalItemInterface *> completeInclude(
int position, unsigned completionOperator,
const TextEditor::AssistInterface *interface,
const ProjectExplorer::HeaderPaths &headerPaths);
TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override; TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *interface) override;
void handleAvailableCompletions(const CodeCompletions &completions); void handleAvailableCompletions(const CodeCompletions &completions);
@@ -65,12 +75,10 @@ private:
TextEditor::IAssistProposal *createProposal(); TextEditor::IAssistProposal *createProposal();
TextEditor::IAssistProposal *createFunctionHintProposal( TextEditor::IAssistProposal *createFunctionHintProposal(
const CodeCompletions &completions); const CodeCompletions &completions);
QList<TextEditor::AssistProposalItemInterface *> toAssistProposalItems( QList<TextEditor::AssistProposalItemInterface *> toAssistProposalItems(
const CodeCompletions &completions) const; const CodeCompletions &completions) const;
bool completeInclude(const QTextCursor &cursor); static QList<TextEditor::AssistProposalItemInterface *> completeIncludePath(
bool completeInclude(int position); const QString &realPath, const QStringList &suffixes, unsigned completionOperator);
void completeIncludePath(const QString &realPath, const QStringList &suffixes);
bool completePreprocessorDirectives(); bool completePreprocessorDirectives();
bool completeDoxygenKeywords(); bool completeDoxygenKeywords();
void addCompletionItem(const QString &text, void addCompletionItem(const QString &text,

View File

@@ -25,6 +25,7 @@
#include "clangdclient.h" #include "clangdclient.h"
#include "clangcompletionassistprocessor.h"
#include "clangcompletioncontextanalyzer.h" #include "clangcompletioncontextanalyzer.h"
#include "clangdiagnosticmanager.h" #include "clangdiagnosticmanager.h"
#include "clangmodelmanagersupport.h" #include "clangmodelmanagersupport.h"
@@ -811,14 +812,15 @@ public:
}; };
enum class CustomAssistMode { Doxygen, Preprocessor }; enum class CustomAssistMode { Doxygen, Preprocessor, IncludePath };
class CustomAssistProcessor : public IAssistProcessor class CustomAssistProcessor : public IAssistProcessor
{ {
public: public:
CustomAssistProcessor(ClangdClient *client, int position, unsigned completionOperator, CustomAssistProcessor(ClangdClient *client, int position, int endPos,
CustomAssistMode mode) unsigned completionOperator, CustomAssistMode mode)
: m_client(client) : m_client(client)
, m_position(position) , m_position(position)
, m_endPos(endPos)
, m_completionOperator(completionOperator) , m_completionOperator(completionOperator)
, m_mode(mode) , m_mode(mode)
{} {}
@@ -834,7 +836,7 @@ private:
CPlusPlus::Icons::keywordIcon()); CPlusPlus::Icons::keywordIcon());
} }
break; break;
case CustomAssistMode::Preprocessor: case CustomAssistMode::Preprocessor: {
static QIcon macroIcon = Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Macro); static QIcon macroIcon = Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Macro);
for (const QString &completion for (const QString &completion
: CppEditor::CppCompletionAssistProcessor::preprocessorCompletions()) { : CppEditor::CppCompletionAssistProcessor::preprocessorCompletions()) {
@@ -844,6 +846,17 @@ private:
completions << createItem("import", macroIcon); completions << createItem("import", macroIcon);
break; 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); GenericProposalModelPtr model(new GenericProposalModel);
model->loadContent(completions); model->loadContent(completions);
const auto proposal = new GenericProposal(m_position, model); const auto proposal = new GenericProposal(m_position, model);
@@ -865,6 +878,7 @@ private:
ClangdClient * const m_client; ClangdClient * const m_client;
const int m_position; const int m_position;
const int m_endPos;
const unsigned m_completionOperator; const unsigned m_completionOperator;
const CustomAssistMode m_mode; const CustomAssistMode m_mode;
}; };
@@ -2816,14 +2830,26 @@ IAssistProcessor *ClangdClient::ClangdCompletionAssistProvider::createProcessor(
qCDebug(clangdLogCompletion) << "creating doxygen processor"; qCDebug(clangdLogCompletion) << "creating doxygen processor";
return new CustomAssistProcessor(m_client, return new CustomAssistProcessor(m_client,
contextAnalyzer.positionForProposal(), contextAnalyzer.positionForProposal(),
contextAnalyzer.positionEndOfExpression(),
contextAnalyzer.completionOperator(), contextAnalyzer.completionOperator(),
CustomAssistMode::Doxygen); CustomAssistMode::Doxygen);
case ClangCompletionContextAnalyzer::CompletePreprocessorDirective: case ClangCompletionContextAnalyzer::CompletePreprocessorDirective:
qCDebug(clangdLogCompletion) << "creating macro processor"; qCDebug(clangdLogCompletion) << "creating macro processor";
return new CustomAssistProcessor(m_client, return new CustomAssistProcessor(m_client,
contextAnalyzer.positionForProposal(), contextAnalyzer.positionForProposal(),
contextAnalyzer.positionEndOfExpression(),
contextAnalyzer.completionOperator(), contextAnalyzer.completionOperator(),
CustomAssistMode::Preprocessor); 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: default:
break; break;
} }

View File

@@ -1488,9 +1488,15 @@ void ClangdTestCompletion::testCompleteIncludeDirective()
getProposal("includeDirectiveCompletion.cpp", proposal); getProposal("includeDirectiveCompletion.cpp", proposal);
QVERIFY(proposal); QVERIFY(proposal);
QVERIFY(hasItem(proposal, " file.h>")); if (client()->versionNumber() < QVersionNumber(14)) {
QVERIFY(hasItem(proposal, " otherFile.h>")); QVERIFY(hasItem(proposal, "file.h"));
QVERIFY(hasItem(proposal, " mylib/")); 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 ")); QVERIFY(!hasSnippet(proposal, "class "));
} }