Clang: Provide all overloads for function definition completion

When we complete a function definition with the parameter list
it is important to have multiple entries to select.

Change-Id: I25e94b58cfe4831387d66eecdec74712addb7ec5
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ivan Donchevskii
2018-07-31 11:44:07 +02:00
parent b1f156ab35
commit 0f96f735f0
5 changed files with 153 additions and 83 deletions

View File

@@ -27,7 +27,6 @@
#include <QTextDocument> #include <QTextDocument>
#include <QTextBlock> #include <QTextBlock>
#include <QTextCursor>
namespace Utils { namespace Utils {
namespace Text { namespace Text {

View File

@@ -29,6 +29,7 @@
#include "utils_global.h" #include "utils_global.h"
#include <QString> #include <QString>
#include <QTextCursor>
QT_FORWARD_DECLARE_CLASS(QTextDocument) QT_FORWARD_DECLARE_CLASS(QTextDocument)
QT_FORWARD_DECLARE_CLASS(QTextCursor) QT_FORWARD_DECLARE_CLASS(QTextCursor)
@@ -54,5 +55,42 @@ QTCREATOR_UTILS_EXPORT QTextCursor flippedCursor(const QTextCursor &cursor);
QTCREATOR_UTILS_EXPORT QTextCursor wordStartCursor(const QTextCursor &cursor); QTCREATOR_UTILS_EXPORT QTextCursor wordStartCursor(const QTextCursor &cursor);
template <class CharacterProvider>
void moveToPrevChar(CharacterProvider &provider, QTextCursor &cursor)
{
cursor.movePosition(QTextCursor::PreviousCharacter);
while (provider.characterAt(cursor.position()).isSpace())
cursor.movePosition(QTextCursor::PreviousCharacter);
}
template <class CharacterProvider>
bool matchPreviousWord(CharacterProvider &provider, QTextCursor cursor, QString pattern)
{
cursor.movePosition(QTextCursor::PreviousWord);
while (provider.characterAt(cursor.position()) == ':')
cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 2);
int previousWordStart = cursor.position();
cursor.movePosition(QTextCursor::NextWord);
moveToPrevChar(provider, cursor);
QString toMatch = provider.textAt(previousWordStart, cursor.position() - previousWordStart + 1);
pattern = pattern.simplified();
while (!pattern.isEmpty() && pattern.endsWith(toMatch)) {
pattern.chop(toMatch.length());
if (pattern.endsWith(' '))
pattern.chop(1);
if (!pattern.isEmpty()) {
cursor.movePosition(QTextCursor::StartOfWord);
cursor.movePosition(QTextCursor::PreviousWord);
previousWordStart = cursor.position();
cursor.movePosition(QTextCursor::NextWord);
moveToPrevChar(provider, cursor);
toMatch = provider.textAt(previousWordStart, cursor.position() - previousWordStart + 1);
}
}
return pattern.isEmpty();
}
} // Text } // Text
} // Utils } // Utils

View File

@@ -57,7 +57,7 @@ bool ClangAssistProposalItem::prematurelyApplies(const QChar &typedCharacter) co
applies = QString::fromLatin1("(,").contains(typedCharacter); applies = QString::fromLatin1("(,").contains(typedCharacter);
else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL)
applies = (typedCharacter == QLatin1Char('/')) && text().endsWith(QLatin1Char('/')); applies = (typedCharacter == QLatin1Char('/')) && text().endsWith(QLatin1Char('/'));
else if (codeCompletion().completionKind == CodeCompletion::ObjCMessageCompletionKind) else if (firstCodeCompletion().completionKind == CodeCompletion::ObjCMessageCompletionKind)
applies = QString::fromLatin1(";.,").contains(typedCharacter); applies = QString::fromLatin1(";.,").contains(typedCharacter);
else else
applies = QString::fromLatin1(";.,:(").contains(typedCharacter); applies = QString::fromLatin1(";.,:(").contains(typedCharacter);
@@ -73,14 +73,6 @@ bool ClangAssistProposalItem::implicitlyApplies() const
return true; return true;
} }
static void moveToPrevChar(TextEditor::TextDocumentManipulatorInterface &manipulator,
QTextCursor &cursor)
{
cursor.movePosition(QTextCursor::PreviousCharacter);
while (manipulator.characterAt(cursor.position()).isSpace())
cursor.movePosition(QTextCursor::PreviousCharacter);
}
static QString textUntilPreviousStatement(TextEditor::TextDocumentManipulatorInterface &manipulator, static QString textUntilPreviousStatement(TextEditor::TextDocumentManipulatorInterface &manipulator,
int startPosition) int startPosition)
{ {
@@ -118,10 +110,35 @@ static bool isAtUsingDeclaration(TextEditor::TextDocumentManipulatorInterface &m
}); });
} }
static QString methodDefinitionParameters(const CodeCompletionChunks &chunks)
{
QString result;
auto typedTextChunkIt = std::find_if(chunks.begin(), chunks.end(),
[](const CodeCompletionChunk &chunk) {
return chunk.kind == CodeCompletionChunk::TypedText;
});
if (typedTextChunkIt == chunks.end())
return result;
std::for_each(++typedTextChunkIt, chunks.end(), [&result](const CodeCompletionChunk &chunk) {
if (chunk.kind == CodeCompletionChunk::Placeholder && chunk.text.contains('=')) {
Utf8String text = chunk.text.mid(0, chunk.text.indexOf('='));
if (text.endsWith(' '))
text.chop(1);
result += text;
} else {
result += chunk.text;
}
});
return result;
}
void ClangAssistProposalItem::apply(TextEditor::TextDocumentManipulatorInterface &manipulator, void ClangAssistProposalItem::apply(TextEditor::TextDocumentManipulatorInterface &manipulator,
int basePosition) const int basePosition) const
{ {
const CodeCompletion ccr = codeCompletion(); const CodeCompletion ccr = firstCodeCompletion();
if (!ccr.requiredFixIts.empty()) { if (!ccr.requiredFixIts.empty()) {
ClangFixItOperation fixItOperation(Utf8String(), ccr.requiredFixIts); ClangFixItOperation fixItOperation(Utf8String(), ccr.requiredFixIts);
@@ -173,40 +190,23 @@ void ClangAssistProposalItem::apply(TextEditor::TextDocumentManipulatorInterface
// inserted closing parenthesis. // inserted closing parenthesis.
const bool skipClosingParenthesis = m_typedCharacter != QLatin1Char('('); const bool skipClosingParenthesis = m_typedCharacter != QLatin1Char('(');
QTextCursor cursor = manipulator.textCursorAt(basePosition); QTextCursor cursor = manipulator.textCursorAt(basePosition);
cursor.movePosition(QTextCursor::PreviousWord);
while (manipulator.characterAt(cursor.position()) == ':')
cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 2);
const int previousWordStart = cursor.position();
// Move to the last character in the previous word
cursor.movePosition(QTextCursor::NextWord);
moveToPrevChar(manipulator, cursor);
const QString previousWord = manipulator.textAt(previousWordStart,
cursor.position() - previousWordStart + 1);
bool abandonParen = false; bool abandonParen = false;
if (previousWord == "&") { if (Utils::Text::matchPreviousWord(manipulator, cursor, "&")) {
moveToPrevChar(manipulator, cursor); Utils::Text::moveToPrevChar(manipulator, cursor);
Utils::Text::moveToPrevChar(manipulator, cursor);
const QChar prevChar = manipulator.characterAt(cursor.position()); const QChar prevChar = manipulator.characterAt(cursor.position());
cursor.setPosition(basePosition);
abandonParen = QString("(;,{}").contains(prevChar); abandonParen = QString("(;,{}").contains(prevChar);
} }
if (!abandonParen) if (!abandonParen)
abandonParen = isAtUsingDeclaration(manipulator, basePosition); abandonParen = isAtUsingDeclaration(manipulator, basePosition);
if (!abandonParen && ccr.completionKind == CodeCompletion::FunctionDefinitionCompletionKind) { if (!abandonParen && ccr.completionKind == CodeCompletion::FunctionDefinitionCompletionKind) {
const CodeCompletionChunk resultType = ccr.chunks.first(); const CodeCompletionChunk resultType = ccr.chunks.first();
QTC_ASSERT(resultType.kind == CodeCompletionChunk::ResultType, return;); QTC_ASSERT(resultType.kind == CodeCompletionChunk::ResultType, return;);
if (previousWord == resultType.text.toString()) { if (Utils::Text::matchPreviousWord(manipulator, cursor, resultType.text.toString())) {
bool skipChunks = true; extraCharacters += methodDefinitionParameters(ccr.chunks);
for (const CodeCompletionChunk &chunk : ccr.chunks) {
if (chunk.kind == CodeCompletionChunk::TypedText) {
skipChunks = false;
continue;
}
if (skipChunks)
continue;
extraCharacters += chunk.text;
}
// To skip the next block. // To skip the next block.
abandonParen = true; abandonParen = true;
} }
@@ -308,10 +308,15 @@ QString ClangAssistProposalItem::text() const
return m_text + (requiresFixIts() ? fixItText() : QString()); return m_text + (requiresFixIts() ? fixItText() : QString());
} }
const QVector<ClangBackEnd::FixItContainer> &ClangAssistProposalItem::firstCompletionFixIts() const
{
return firstCodeCompletion().requiredFixIts;
}
// FIXME: Indicate required fix-it without adding extra text. // FIXME: Indicate required fix-it without adding extra text.
QString ClangAssistProposalItem::fixItText() const QString ClangAssistProposalItem::fixItText() const
{ {
const FixItContainer &fixIt = m_codeCompletion.requiredFixIts.first(); const FixItContainer &fixIt = firstCompletionFixIts().first();
const SourceRangeContainer &range = fixIt.range; const SourceRangeContainer &range = fixIt.range;
return QCoreApplication::translate("ClangCodeModel::ClangAssistProposalItem", return QCoreApplication::translate("ClangCodeModel::ClangAssistProposalItem",
" (requires to correct [%1:%2-%3:%4] to \"%5\")") " (requires to correct [%1:%2-%3:%4] to \"%5\")")
@@ -325,12 +330,13 @@ QString ClangAssistProposalItem::fixItText() const
int ClangAssistProposalItem::fixItsShift( int ClangAssistProposalItem::fixItsShift(
const TextEditor::TextDocumentManipulatorInterface &manipulator) const const TextEditor::TextDocumentManipulatorInterface &manipulator) const
{ {
if (m_codeCompletion.requiredFixIts.empty()) const QVector<ClangBackEnd::FixItContainer> &requiredFixIts = firstCompletionFixIts();
if (requiredFixIts.empty())
return 0; return 0;
int shift = 0; int shift = 0;
QTextCursor cursor = manipulator.textCursorAt(0); QTextCursor cursor = manipulator.textCursorAt(0);
for (const FixItContainer &fixIt : m_codeCompletion.requiredFixIts) { for (const FixItContainer &fixIt : requiredFixIts) {
const int fixItStartPos = Utils::Text::positionInText( const int fixItStartPos = Utils::Text::positionInText(
cursor.document(), cursor.document(),
static_cast<int>(fixIt.range.start.line), static_cast<int>(fixIt.range.start.line),
@@ -350,7 +356,8 @@ QIcon ClangAssistProposalItem::icon() const
static const char SNIPPET_ICON_PATH[] = ":/texteditor/images/snippet.png"; static const char SNIPPET_ICON_PATH[] = ":/texteditor/images/snippet.png";
static const QIcon snippetIcon = QIcon(QLatin1String(SNIPPET_ICON_PATH)); static const QIcon snippetIcon = QIcon(QLatin1String(SNIPPET_ICON_PATH));
switch (m_codeCompletion.completionKind) { const ClangBackEnd::CodeCompletion &completion = firstCodeCompletion();
switch (completion.completionKind) {
case CodeCompletion::ClassCompletionKind: case CodeCompletion::ClassCompletionKind:
case CodeCompletion::TemplateClassCompletionKind: case CodeCompletion::TemplateClassCompletionKind:
case CodeCompletion::TypeAliasCompletionKind: case CodeCompletion::TypeAliasCompletionKind:
@@ -365,7 +372,7 @@ QIcon ClangAssistProposalItem::icon() const
case CodeCompletion::FunctionDefinitionCompletionKind: case CodeCompletion::FunctionDefinitionCompletionKind:
case CodeCompletion::TemplateFunctionCompletionKind: case CodeCompletion::TemplateFunctionCompletionKind:
case CodeCompletion::ObjCMessageCompletionKind: case CodeCompletion::ObjCMessageCompletionKind:
switch (m_codeCompletion.availability) { switch (completion.availability) {
case CodeCompletion::Available: case CodeCompletion::Available:
case CodeCompletion::Deprecated: case CodeCompletion::Deprecated:
return Icons::iconForType(Icons::FuncPublicIconType); return Icons::iconForType(Icons::FuncPublicIconType);
@@ -375,7 +382,7 @@ QIcon ClangAssistProposalItem::icon() const
case CodeCompletion::SignalCompletionKind: case CodeCompletion::SignalCompletionKind:
return Icons::iconForType(Icons::SignalIconType); return Icons::iconForType(Icons::SignalIconType);
case CodeCompletion::SlotCompletionKind: case CodeCompletion::SlotCompletionKind:
switch (m_codeCompletion.availability) { switch (completion.availability) {
case CodeCompletion::Available: case CodeCompletion::Available:
case CodeCompletion::Deprecated: case CodeCompletion::Deprecated:
return Icons::iconForType(Icons::SlotPublicIconType); return Icons::iconForType(Icons::SlotPublicIconType);
@@ -389,7 +396,7 @@ QIcon ClangAssistProposalItem::icon() const
case CodeCompletion::PreProcessorCompletionKind: case CodeCompletion::PreProcessorCompletionKind:
return Icons::iconForType(Icons::MacroIconType); return Icons::iconForType(Icons::MacroIconType);
case CodeCompletion::VariableCompletionKind: case CodeCompletion::VariableCompletionKind:
switch (m_codeCompletion.availability) { switch (completion.availability) {
case CodeCompletion::Available: case CodeCompletion::Available:
case CodeCompletion::Deprecated: case CodeCompletion::Deprecated:
return Icons::iconForType(Icons::VarPublicIconType); return Icons::iconForType(Icons::VarPublicIconType);
@@ -411,11 +418,15 @@ QIcon ClangAssistProposalItem::icon() const
QString ClangAssistProposalItem::detail() const QString ClangAssistProposalItem::detail() const
{ {
QString detail = CompletionChunksToTextConverter::convertToToolTipWithHtml( QString detail;
m_codeCompletion.chunks, m_codeCompletion.completionKind); for (const ClangBackEnd::CodeCompletion &codeCompletion : m_codeCompletions) {
if (!detail.isEmpty())
if (!m_codeCompletion.briefComment.isEmpty()) detail += "<br>";
detail += QStringLiteral("\n\n") + m_codeCompletion.briefComment.toString(); detail += CompletionChunksToTextConverter::convertToToolTipWithHtml(
codeCompletion.chunks, codeCompletion.completionKind);
if (!codeCompletion.briefComment.isEmpty())
detail += "<br>" + codeCompletion.briefComment.toString();
}
return detail; return detail;
} }
@@ -437,7 +448,7 @@ quint64 ClangAssistProposalItem::hash() const
bool ClangAssistProposalItem::requiresFixIts() const bool ClangAssistProposalItem::requiresFixIts() const
{ {
return !m_codeCompletion.requiredFixIts.empty(); return !firstCompletionFixIts().empty();
} }
bool ClangAssistProposalItem::hasOverloadsWithParameters() const bool ClangAssistProposalItem::hasOverloadsWithParameters() const
@@ -455,14 +466,14 @@ void ClangAssistProposalItem::keepCompletionOperator(unsigned compOp)
m_completionOperator = compOp; m_completionOperator = compOp;
} }
void ClangAssistProposalItem::setCodeCompletion(const CodeCompletion &codeCompletion) void ClangAssistProposalItem::appendCodeCompletion(const CodeCompletion &codeCompletion)
{ {
m_codeCompletion = codeCompletion; m_codeCompletions.push_back(codeCompletion);
} }
const ClangBackEnd::CodeCompletion &ClangAssistProposalItem::codeCompletion() const const ClangBackEnd::CodeCompletion &ClangAssistProposalItem::firstCodeCompletion() const
{ {
return m_codeCompletion; return m_codeCompletions.at(0);
} }
} // namespace Internal } // namespace Internal

View File

@@ -57,14 +57,15 @@ public:
bool hasOverloadsWithParameters() const; bool hasOverloadsWithParameters() const;
void setHasOverloadsWithParameters(bool hasOverloadsWithParameters); void setHasOverloadsWithParameters(bool hasOverloadsWithParameters);
void setCodeCompletion(const ClangBackEnd::CodeCompletion &codeCompletion); void appendCodeCompletion(const ClangBackEnd::CodeCompletion &firstCodeCompletion);
const ClangBackEnd::CodeCompletion &codeCompletion() const; const ClangBackEnd::CodeCompletion &firstCodeCompletion() const;
private: private:
const QVector<ClangBackEnd::FixItContainer> &firstCompletionFixIts() const;
QString fixItText() const; QString fixItText() const;
int fixItsShift(const TextEditor::TextDocumentManipulatorInterface &manipulator) const; int fixItsShift(const TextEditor::TextDocumentManipulatorInterface &manipulator) const;
ClangBackEnd::CodeCompletion m_codeCompletion; std::vector<ClangBackEnd::CodeCompletion> m_codeCompletions;
QList<ClangBackEnd::CodeCompletion> m_overloads; QList<ClangBackEnd::CodeCompletion> m_overloads;
bool m_hasOverloadsWithParameters = false; bool m_hasOverloadsWithParameters = false;
QString m_text; QString m_text;

View File

@@ -64,14 +64,51 @@ namespace Internal {
using ClangBackEnd::CodeCompletion; using ClangBackEnd::CodeCompletion;
using TextEditor::AssistProposalItemInterface; using TextEditor::AssistProposalItemInterface;
namespace { static void addAssistProposalItem(QList<AssistProposalItemInterface *> &items,
const CodeCompletion &codeCompletion,
const QString &name)
{
ClangAssistProposalItem *item = new ClangAssistProposalItem;
items.push_back(item);
QList<AssistProposalItemInterface *> toAssistProposalItems(const CodeCompletions &completions) item->setText(name);
item->setOrder(int(codeCompletion.priority));
item->appendCodeCompletion(codeCompletion);
}
static void addFunctionOverloadAssistProposalItem(QList<AssistProposalItemInterface *> &items,
const ClangCompletionAssistInterface *interface,
const CodeCompletion &codeCompletion,
const QString &name)
{
ClangBackEnd::CodeCompletionChunk resultType = codeCompletion.chunks.first();
QTC_ASSERT(resultType.kind == ClangBackEnd::CodeCompletionChunk::ResultType,
return;);
auto *item = static_cast<ClangAssistProposalItem *>(items.last());
item->setHasOverloadsWithParameters(true);
QTextCursor cursor = interface->textEditorWidget()->textCursor();
cursor.setPosition(interface->position());
cursor.movePosition(QTextCursor::StartOfWord);
if (::Utils::Text::matchPreviousWord(*interface->textEditorWidget(),
cursor,
resultType.text.toString())) {
addAssistProposalItem(items, codeCompletion, name);
} else {
item->appendCodeCompletion(codeCompletion);
}
}
static QList<AssistProposalItemInterface *> toAssistProposalItems(
const CodeCompletions &completions,
const ClangCompletionAssistInterface *interface)
{ {
bool signalCompletion = false; // TODO bool signalCompletion = false; // TODO
bool slotCompletion = false; // TODO bool slotCompletion = false; // TODO
QHash<QString, ClangAssistProposalItem *> items; QList<AssistProposalItemInterface *> items;
items.reserve(completions.size());
for (const CodeCompletion &codeCompletion : completions) { for (const CodeCompletion &codeCompletion : completions) {
if (codeCompletion.text.isEmpty()) // TODO: Make isValid()? if (codeCompletion.text.isEmpty()) // TODO: Make isValid()?
continue; continue;
@@ -80,35 +117,19 @@ QList<AssistProposalItemInterface *> toAssistProposalItems(const CodeCompletions
if (slotCompletion && codeCompletion.completionKind != CodeCompletion::SlotCompletionKind) if (slotCompletion && codeCompletion.completionKind != CodeCompletion::SlotCompletionKind)
continue; continue;
QString name; const QString name = codeCompletion.completionKind == CodeCompletion::KeywordCompletionKind
if (codeCompletion.completionKind == CodeCompletion::KeywordCompletionKind) ? CompletionChunksToTextConverter::convertToName(codeCompletion.chunks)
name = CompletionChunksToTextConverter::convertToName(codeCompletion.chunks); : codeCompletion.text.toString();
if (!items.empty() && items.last()->text() == name && codeCompletion.hasParameters)
addFunctionOverloadAssistProposalItem(items, interface, codeCompletion, name);
else else
name = codeCompletion.text.toString(); addAssistProposalItem(items, codeCompletion, name);
ClangAssistProposalItem *item = items.value(name, 0);
if (item) {
if (codeCompletion.hasParameters)
item->setHasOverloadsWithParameters(true);
} else {
item = new ClangAssistProposalItem;
items.insert(name, item);
item->setText(name);
item->setOrder(int(codeCompletion.priority));
item->setCodeCompletion(codeCompletion);
}
} }
QList<AssistProposalItemInterface *> results; return items;
results.reserve(items.size());
std::copy(items.cbegin(), items.cend(), std::back_inserter(results));
return results;
} }
} // Anonymous
using namespace CPlusPlus; using namespace CPlusPlus;
using namespace TextEditor; using namespace TextEditor;
@@ -147,7 +168,7 @@ void ClangCompletionAssistProcessor::handleAvailableCompletions(
QTC_CHECK(m_completions.isEmpty()); QTC_CHECK(m_completions.isEmpty());
if (m_sentRequestType == NormalCompletion) { if (m_sentRequestType == NormalCompletion) {
m_completions = toAssistProposalItems(completions); m_completions = toAssistProposalItems(completions, m_interface.data());
if (m_addSnippets && !m_completions.isEmpty()) if (m_addSnippets && !m_completions.isEmpty())
addSnippets(); addSnippets();