forked from qt-creator/qt-creator
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:
@@ -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()
|
||||||
|
@@ -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,
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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 "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user