summaryrefslogtreecommitdiff
path: root/src/plugins/cppeditor/cppcompletionassist.cpp
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2021-08-30 10:58:08 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2021-09-01 14:53:58 +0000
commit284817fae6514701902ccdb834c2faa46462f2e8 (patch)
tree44a8c7d9813dc110b61c4639036366c7696bd7e9 /src/plugins/cppeditor/cppcompletionassist.cpp
parent3e1fa0f170d523971d2c3c12da15a6e291f56511 (diff)
downloadqt-creator-284817fae6514701902ccdb834c2faa46462f2e8.tar.gz
Merge CppTools into CppEditor
There was no proper separation of responsibilities between these plugins. In particular, CppTools had lots of editor-related functionality, so it's not clear why it was separated out in the first place. In fact, for a lot of code, it seemed quite arbitrary where it was put (just one example: switchHeaderSource() was in CppTools, wheras switchDeclarationDefinition() was in CppEditor). Merging the plugins will enable us to get rid of various convoluted pseudo-abstractions that were only introduced to keep up the artificial separation. Change-Id: Iafc3bce625b4794f6d4aa03df6cddc7f2d26716a Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Diffstat (limited to 'src/plugins/cppeditor/cppcompletionassist.cpp')
-rw-r--r--src/plugins/cppeditor/cppcompletionassist.cpp2134
1 files changed, 2134 insertions, 0 deletions
diff --git a/src/plugins/cppeditor/cppcompletionassist.cpp b/src/plugins/cppeditor/cppcompletionassist.cpp
new file mode 100644
index 0000000000..88e5e0d865
--- /dev/null
+++ b/src/plugins/cppeditor/cppcompletionassist.cpp
@@ -0,0 +1,2134 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "cppcompletionassist.h"
+
+#include "builtineditordocumentparser.h"
+#include "cppdoxygen.h"
+#include "cppeditorconstants.h"
+#include "cppmodelmanager.h"
+#include "cpptoolsreuse.h"
+#include "editordocumenthandle.h"
+
+#include <coreplugin/icore.h>
+#include <cppeditor/cppeditorconstants.h>
+#include <texteditor/codeassist/assistproposalitem.h>
+#include <texteditor/codeassist/genericproposal.h>
+#include <texteditor/codeassist/ifunctionhintproposalmodel.h>
+#include <texteditor/codeassist/functionhintproposal.h>
+#include <texteditor/snippets/snippet.h>
+#include <texteditor/texteditorsettings.h>
+#include <texteditor/completionsettings.h>
+
+#include <utils/textutils.h>
+#include <utils/mimetypes/mimedatabase.h>
+#include <utils/qtcassert.h>
+
+#include <cplusplus/BackwardsScanner.h>
+#include <cplusplus/CppRewriter.h>
+#include <cplusplus/ExpressionUnderCursor.h>
+#include <cplusplus/MatchingText.h>
+#include <cplusplus/Overview.h>
+#include <cplusplus/ResolveExpression.h>
+
+#include <QDirIterator>
+#include <QLatin1String>
+#include <QTextCursor>
+#include <QTextDocument>
+#include <QIcon>
+
+using namespace CPlusPlus;
+using namespace CppEditor;
+using namespace TextEditor;
+
+namespace CppEditor::Internal {
+
+struct CompleteFunctionDeclaration
+{
+ explicit CompleteFunctionDeclaration(Function *f = nullptr)
+ : function(f)
+ {}
+
+ Function *function;
+};
+
+// ---------------------
+// CppAssistProposalItem
+// ---------------------
+class CppAssistProposalItem final : public AssistProposalItem
+{
+public:
+ ~CppAssistProposalItem() noexcept override = default;
+ bool prematurelyApplies(const QChar &c) const override;
+ void applyContextualContent(TextDocumentManipulatorInterface &manipulator, int basePosition) const override;
+
+ bool isOverloaded() const { return m_isOverloaded; }
+ void markAsOverloaded() { m_isOverloaded = true; }
+ void keepCompletionOperator(unsigned compOp) { m_completionOperator = compOp; }
+ void keepTypeOfExpression(const QSharedPointer<TypeOfExpression> &typeOfExp)
+ { m_typeOfExpression = typeOfExp; }
+ bool isKeyword() const final
+ { return m_isKeyword; }
+ void setIsKeyword(bool isKeyword)
+ { m_isKeyword = isKeyword; }
+
+ quint64 hash() const override;
+
+private:
+ QSharedPointer<TypeOfExpression> m_typeOfExpression;
+ unsigned m_completionOperator = T_EOF_SYMBOL;
+ mutable QChar m_typedChar;
+ bool m_isOverloaded = false;
+ bool m_isKeyword = false;
+};
+
+} // CppEditor::Internal
+
+Q_DECLARE_METATYPE(CppEditor::Internal::CompleteFunctionDeclaration)
+
+namespace CppEditor::Internal {
+
+bool CppAssistProposalModel::isSortable(const QString &prefix) const
+{
+ if (m_completionOperator != T_EOF_SYMBOL)
+ return true;
+
+ return !prefix.isEmpty();
+}
+
+AssistProposalItemInterface *CppAssistProposalModel::proposalItem(int index) const
+{
+ AssistProposalItemInterface *item = GenericProposalModel::proposalItem(index);
+ if (!item->isSnippet()) {
+ auto cppItem = static_cast<CppAssistProposalItem *>(item);
+ cppItem->keepCompletionOperator(m_completionOperator);
+ cppItem->keepTypeOfExpression(m_typeOfExpression);
+ }
+ return item;
+}
+
+bool CppAssistProposalItem::prematurelyApplies(const QChar &typedChar) const
+{
+ if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
+ if (typedChar == QLatin1Char('(') || typedChar == QLatin1Char(',')) {
+ m_typedChar = typedChar;
+ return true;
+ }
+ } else if (m_completionOperator == T_STRING_LITERAL
+ || m_completionOperator == T_ANGLE_STRING_LITERAL) {
+ if (typedChar == QLatin1Char('/') && text().endsWith(QLatin1Char('/'))) {
+ m_typedChar = typedChar;
+ return true;
+ }
+ } else if (data().value<Symbol *>()) {
+ if (typedChar == QLatin1Char(':')
+ || typedChar == QLatin1Char(';')
+ || typedChar == QLatin1Char('.')
+ || typedChar == QLatin1Char(',')
+ || typedChar == QLatin1Char('(')) {
+ m_typedChar = typedChar;
+ return true;
+ }
+ } else if (data().canConvert<CompleteFunctionDeclaration>()) {
+ if (typedChar == QLatin1Char('(')) {
+ m_typedChar = typedChar;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool isDereferenced(TextDocumentManipulatorInterface &manipulator, int basePosition)
+{
+ QTextCursor cursor = manipulator.textCursorAt(basePosition);
+ cursor.setPosition(basePosition);
+
+ BackwardsScanner scanner(cursor, LanguageFeatures());
+ for (int pos = scanner.startToken()-1; pos >= 0; pos--) {
+ switch (scanner[pos].kind()) {
+ case T_COLON_COLON:
+ case T_IDENTIFIER:
+ //Ignore scope specifiers
+ break;
+
+ case T_AMPER: return true;
+ default: return false;
+ }
+ }
+ return false;
+}
+
+quint64 CppAssistProposalItem::hash() const
+{
+ if (data().canConvert<Symbol *>())
+ return quint64(data().value<Symbol *>()->index());
+ else if (data().canConvert<CompleteFunctionDeclaration>())
+ return quint64(data().value<CompleteFunctionDeclaration>().function->index());
+
+ return 0;
+}
+
+void CppAssistProposalItem::applyContextualContent(TextDocumentManipulatorInterface &manipulator, int basePosition) const
+{
+ Symbol *symbol = nullptr;
+
+ if (data().isValid())
+ symbol = data().value<Symbol *>();
+
+ QString toInsert;
+ QString extraChars;
+ int extraLength = 0;
+ int cursorOffset = 0;
+ bool setAutoCompleteSkipPos = false;
+
+ bool autoParenthesesEnabled = true;
+
+ if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
+ toInsert = text();
+ extraChars += QLatin1Char(')');
+
+ if (m_typedChar == QLatin1Char('(')) // Eat the opening parenthesis
+ m_typedChar = QChar();
+ } else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) {
+ toInsert = text();
+ if (!toInsert.endsWith(QLatin1Char('/'))) {
+ extraChars += QLatin1Char((m_completionOperator == T_ANGLE_STRING_LITERAL) ? '>' : '"');
+ } else {
+ if (m_typedChar == QLatin1Char('/')) // Eat the slash
+ m_typedChar = QChar();
+ }
+ } else {
+ toInsert = text();
+
+ const CompletionSettings &completionSettings = TextEditorSettings::completionSettings();
+ const bool autoInsertBrackets = completionSettings.m_autoInsertBrackets;
+
+ if (autoInsertBrackets && symbol && symbol->type()) {
+ if (Function *function = symbol->type()->asFunctionType()) {
+ // If the member is a function, automatically place the opening parenthesis,
+ // except when it might take template parameters.
+ if (!function->hasReturnType()
+ && (function->unqualifiedName()
+ && !function->unqualifiedName()->isDestructorNameId())) {
+ // Don't insert any magic, since the user might have just wanted to select the class
+
+ /// ### port me
+#if 0
+ } else if (function->templateParameterCount() != 0 && typedChar != QLatin1Char('(')) {
+ // If there are no arguments, then we need the template specification
+ if (function->argumentCount() == 0)
+ extraChars += QLatin1Char('<');
+#endif
+ } else if (!isDereferenced(manipulator, basePosition) && !function->isAmbiguous()) {
+ // When the user typed the opening parenthesis, he'll likely also type the closing one,
+ // in which case it would be annoying if we put the cursor after the already automatically
+ // inserted closing parenthesis.
+ const bool skipClosingParenthesis = m_typedChar != QLatin1Char('(');
+
+ if (completionSettings.m_spaceAfterFunctionName)
+ extraChars += QLatin1Char(' ');
+ extraChars += QLatin1Char('(');
+ if (m_typedChar == QLatin1Char('('))
+ m_typedChar = QChar();
+
+ // If the function doesn't return anything, automatically place the semicolon,
+ // unless we're doing a scope completion (then it might be function definition).
+ const QChar characterAtCursor = manipulator.characterAt(manipulator.currentPosition());
+ bool endWithSemicolon = m_typedChar == QLatin1Char(';')
+ || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON);
+ const QChar semicolon = m_typedChar.isNull() ? QLatin1Char(';') : m_typedChar;
+
+ if (endWithSemicolon && characterAtCursor == semicolon) {
+ endWithSemicolon = false;
+ m_typedChar = QChar();
+ }
+
+ // If the function takes no arguments, automatically place the closing parenthesis
+ if (!isOverloaded() && !function->hasArguments() && skipClosingParenthesis) {
+ extraChars += QLatin1Char(')');
+ if (endWithSemicolon) {
+ extraChars += semicolon;
+ m_typedChar = QChar();
+ }
+ } else if (autoParenthesesEnabled) {
+ const QChar lookAhead = manipulator.characterAt(manipulator.currentPosition() + 1);
+ if (MatchingText::shouldInsertMatchingText(lookAhead)) {
+ extraChars += QLatin1Char(')');
+ --cursorOffset;
+ setAutoCompleteSkipPos = true;
+ if (endWithSemicolon) {
+ extraChars += semicolon;
+ --cursorOffset;
+ m_typedChar = QChar();
+ }
+ }
+ // TODO: When an opening parenthesis exists, the "semicolon" should really be
+ // inserted after the matching closing parenthesis.
+ }
+ }
+ }
+ }
+
+ if (autoInsertBrackets && data().canConvert<CompleteFunctionDeclaration>()) {
+ if (m_typedChar == QLatin1Char('('))
+ m_typedChar = QChar();
+
+ // everything from the closing parenthesis on are extra chars, to
+ // make sure an auto-inserted ")" gets replaced by ") const" if necessary
+ int closingParen = toInsert.lastIndexOf(QLatin1Char(')'));
+ extraChars = toInsert.mid(closingParen);
+ toInsert.truncate(closingParen);
+ }
+ }
+
+ // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before
+ if (!m_typedChar.isNull()) {
+ extraChars += m_typedChar;
+ if (cursorOffset != 0)
+ --cursorOffset;
+ }
+
+ // Avoid inserting characters that are already there
+ int currentPosition = manipulator.currentPosition();
+ QTextCursor cursor = manipulator.textCursorAt(basePosition);
+ cursor.movePosition(QTextCursor::EndOfWord);
+ const QString textAfterCursor = manipulator.textAt(currentPosition,
+ cursor.position() - currentPosition);
+ if (toInsert != textAfterCursor
+ && toInsert.indexOf(textAfterCursor, currentPosition - basePosition) >= 0) {
+ currentPosition = cursor.position();
+ }
+
+ for (int i = 0; i < extraChars.length(); ++i) {
+ const QChar a = extraChars.at(i);
+ const QChar b = manipulator.characterAt(currentPosition + i);
+ if (a == b)
+ ++extraLength;
+ else
+ break;
+ }
+
+ toInsert += extraChars;
+
+ // Insert the remainder of the name
+ const int length = currentPosition - basePosition + extraLength;
+ manipulator.replace(basePosition, length, toInsert);
+ manipulator.setCursorPosition(basePosition + toInsert.length());
+ if (cursorOffset)
+ manipulator.setCursorPosition(manipulator.currentPosition() + cursorOffset);
+ if (setAutoCompleteSkipPos)
+ manipulator.setAutoCompleteSkipPosition(manipulator.currentPosition());
+}
+
+// --------------------
+// CppFunctionHintModel
+// --------------------
+class CppFunctionHintModel : public IFunctionHintProposalModel
+{
+public:
+ CppFunctionHintModel(const QList<Function *> &functionSymbols,
+ const QSharedPointer<TypeOfExpression> &typeOfExp)
+ : m_functionSymbols(functionSymbols)
+ , m_currentArg(-1)
+ , m_typeOfExpression(typeOfExp)
+ {}
+
+ void reset() override {}
+ int size() const override { return m_functionSymbols.size(); }
+ QString text(int index) const override;
+ int activeArgument(const QString &prefix) const override;
+
+private:
+ QList<Function *> m_functionSymbols;
+ mutable int m_currentArg;
+ QSharedPointer<TypeOfExpression> m_typeOfExpression;
+};
+
+QString CppFunctionHintModel::text(int index) const
+{
+ Overview overview;
+ overview.showReturnTypes = true;
+ overview.showArgumentNames = true;
+ overview.markedArgument = m_currentArg + 1;
+ Function *f = m_functionSymbols.at(index);
+
+ const QString prettyMethod = overview.prettyType(f->type(), f->name());
+ const int begin = overview.markedArgumentBegin;
+ const int end = overview.markedArgumentEnd;
+
+ QString hintText;
+ hintText += prettyMethod.left(begin).toHtmlEscaped();
+ hintText += QLatin1String("<b>");
+ hintText += prettyMethod.mid(begin, end - begin).toHtmlEscaped();
+ hintText += QLatin1String("</b>");
+ hintText += prettyMethod.mid(end).toHtmlEscaped();
+ return hintText;
+}
+
+int CppFunctionHintModel::activeArgument(const QString &prefix) const
+{
+ int argnr = 0;
+ int parcount = 0;
+ SimpleLexer tokenize;
+ Tokens tokens = tokenize(prefix);
+ for (int i = 0; i < tokens.count(); ++i) {
+ const Token &tk = tokens.at(i);
+ if (tk.is(T_LPAREN))
+ ++parcount;
+ else if (tk.is(T_RPAREN))
+ --parcount;
+ else if (!parcount && tk.is(T_COMMA))
+ ++argnr;
+ }
+
+ if (parcount < 0)
+ return -1;
+
+ if (argnr != m_currentArg)
+ m_currentArg = argnr;
+
+ return argnr;
+}
+
+// ---------------------------
+// InternalCompletionAssistProvider
+// ---------------------------
+
+IAssistProcessor *InternalCompletionAssistProvider::createProcessor() const
+{
+ return new InternalCppCompletionAssistProcessor;
+}
+
+AssistInterface *InternalCompletionAssistProvider::createAssistInterface(
+ const Utils::FilePath &filePath,
+ const TextEditorWidget *textEditorWidget,
+ const LanguageFeatures &languageFeatures,
+ int position,
+ AssistReason reason) const
+{
+ QTC_ASSERT(textEditorWidget, return nullptr);
+
+ return new CppCompletionAssistInterface(filePath,
+ textEditorWidget,
+ BuiltinEditorDocumentParser::get(filePath.toString()),
+ languageFeatures,
+ position,
+ reason,
+ CppModelManager::instance()->workingCopy());
+}
+
+// -----------------
+// CppAssistProposal
+// -----------------
+class CppAssistProposal : public GenericProposal
+{
+public:
+ CppAssistProposal(int cursorPos, GenericProposalModelPtr model)
+ : GenericProposal(cursorPos, model)
+ , m_replaceDotForArrow(model.staticCast<CppAssistProposalModel>()->m_replaceDotForArrow)
+ {}
+
+ bool isCorrective(TextEditorWidget *) const override { return m_replaceDotForArrow; }
+ void makeCorrection(TextEditorWidget *editorWidget) override;
+
+private:
+ bool m_replaceDotForArrow;
+};
+
+void CppAssistProposal::makeCorrection(TextEditorWidget *editorWidget)
+{
+ const int oldPosition = editorWidget->position();
+ editorWidget->setCursorPosition(basePosition() - 1);
+ editorWidget->replace(1, QLatin1String("->"));
+ editorWidget->setCursorPosition(oldPosition + 1);
+ moveBasePosition(1);
+}
+
+namespace {
+
+class ConvertToCompletionItem: protected NameVisitor
+{
+ // The completion item.
+ AssistProposalItem *_item = nullptr;
+
+ // The current symbol.
+ Symbol *_symbol = nullptr;
+
+ // The pretty printer.
+ Overview overview;
+
+public:
+ ConvertToCompletionItem()
+ {
+ overview.showReturnTypes = true;
+ overview.showArgumentNames = true;
+ }
+
+ AssistProposalItem *operator()(Symbol *symbol)
+ {
+ //using declaration can be qualified
+ if (!symbol || !symbol->name() || (symbol->name()->isQualifiedNameId()
+ && !symbol->asUsingDeclaration()))
+ return nullptr;
+
+ AssistProposalItem *previousItem = switchCompletionItem(nullptr);
+ Symbol *previousSymbol = switchSymbol(symbol);
+ accept(symbol->unqualifiedName());
+ if (_item)
+ _item->setData(QVariant::fromValue(symbol));
+ (void) switchSymbol(previousSymbol);
+ return switchCompletionItem(previousItem);
+ }
+
+protected:
+ Symbol *switchSymbol(Symbol *symbol)
+ {
+ Symbol *previousSymbol = _symbol;
+ _symbol = symbol;
+ return previousSymbol;
+ }
+
+ AssistProposalItem *switchCompletionItem(AssistProposalItem *item)
+ {
+ AssistProposalItem *previousItem = _item;
+ _item = item;
+ return previousItem;
+ }
+
+ AssistProposalItem *newCompletionItem(const Name *name)
+ {
+ AssistProposalItem *item = new CppAssistProposalItem;
+ item->setText(overview.prettyName(name));
+ return item;
+ }
+
+ void visit(const Identifier *name) override
+ {
+ _item = newCompletionItem(name);
+ if (!_symbol->isScope() || _symbol->isFunction())
+ _item->setDetail(overview.prettyType(_symbol->type(), name));
+ }
+
+ void visit(const TemplateNameId *name) override
+ {
+ _item = newCompletionItem(name);
+ _item->setText(QString::fromUtf8(name->identifier()->chars(), name->identifier()->size()));
+ }
+
+ void visit(const DestructorNameId *name) override
+ { _item = newCompletionItem(name); }
+
+ void visit(const OperatorNameId *name) override
+ {
+ _item = newCompletionItem(name);
+ _item->setDetail(overview.prettyType(_symbol->type(), name));
+ }
+
+ void visit(const ConversionNameId *name) override
+ { _item = newCompletionItem(name); }
+
+ void visit(const QualifiedNameId *name) override
+ { _item = newCompletionItem(name->name()); }
+};
+
+Class *asClassOrTemplateClassType(FullySpecifiedType ty)
+{
+ if (Class *classTy = ty->asClassType())
+ return classTy;
+ if (Template *templ = ty->asTemplateType()) {
+ if (Symbol *decl = templ->declaration())
+ return decl->asClass();
+ }
+ return nullptr;
+}
+
+Scope *enclosingNonTemplateScope(Symbol *symbol)
+{
+ if (symbol) {
+ if (Scope *scope = symbol->enclosingScope()) {
+ if (Template *templ = scope->asTemplate())
+ return templ->enclosingScope();
+ return scope;
+ }
+ }
+ return nullptr;
+}
+
+Function *asFunctionOrTemplateFunctionType(FullySpecifiedType ty)
+{
+ if (Function *funTy = ty->asFunctionType())
+ return funTy;
+ if (Template *templ = ty->asTemplateType()) {
+ if (Symbol *decl = templ->declaration())
+ return decl->asFunction();
+ }
+ return nullptr;
+}
+
+bool isQPrivateSignal(const Symbol *symbol)
+{
+ if (!symbol)
+ return false;
+
+ static Identifier qPrivateSignalIdentifier("QPrivateSignal", 14);
+
+ if (FullySpecifiedType type = symbol->type()) {
+ if (NamedType *namedType = type->asNamedType()) {
+ if (const Name *name = namedType->name()) {
+ if (name->match(&qPrivateSignalIdentifier))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+QString createQt4SignalOrSlot(CPlusPlus::Function *function, const Overview &overview)
+{
+ QString signature;
+ signature += Overview().prettyName(function->name());
+ signature += QLatin1Char('(');
+ for (unsigned i = 0, to = function->argumentCount(); i < to; ++i) {
+ Symbol *arg = function->argumentAt(i);
+ if (isQPrivateSignal(arg))
+ continue;
+ if (i != 0)
+ signature += QLatin1Char(',');
+ signature += overview.prettyType(arg->type());
+ }
+ signature += QLatin1Char(')');
+
+ const QByteArray normalized = QMetaObject::normalizedSignature(signature.toUtf8());
+ return QString::fromUtf8(normalized, normalized.size());
+}
+
+QString createQt5SignalOrSlot(CPlusPlus::Function *function, const Overview &overview)
+{
+ QString text;
+ text += overview.prettyName(function->name());
+ return text;
+}
+
+/*!
+ \class BackwardsEater
+ \brief Checks strings and expressions before given position.
+
+ Similar to BackwardsScanner, but also can handle expressions. Ignores whitespace.
+*/
+class BackwardsEater
+{
+public:
+ explicit BackwardsEater(const CppCompletionAssistInterface *assistInterface, int position)
+ : m_position(position)
+ , m_assistInterface(assistInterface)
+ {
+ }
+
+ bool isPositionValid() const
+ {
+ return m_position >= 0;
+ }
+
+ bool eatConnectOpenParenthesis()
+ {
+ return eatString(QLatin1String("(")) && eatString(QLatin1String("connect"));
+ }
+
+ bool eatExpressionCommaAmpersand()
+ {
+ return eatString(QLatin1String("&")) && eatString(QLatin1String(",")) && eatExpression();
+ }
+
+ bool eatConnectOpenParenthesisExpressionCommaAmpersandExpressionComma()
+ {
+ return eatString(QLatin1String(","))
+ && eatExpression()
+ && eatExpressionCommaAmpersand()
+ && eatConnectOpenParenthesis();
+ }
+
+private:
+ bool eatExpression()
+ {
+ if (!isPositionValid())
+ return false;
+
+ maybeEatWhitespace();
+
+ QTextCursor cursor(m_assistInterface->textDocument());
+ cursor.setPosition(m_position + 1);
+ ExpressionUnderCursor expressionUnderCursor(m_assistInterface->languageFeatures());
+ const QString expression = expressionUnderCursor(cursor);
+ if (expression.isEmpty())
+ return false;
+ m_position = m_position - expression.length();
+ return true;
+ }
+
+ bool eatString(const QString &string)
+ {
+ if (!isPositionValid())
+ return false;
+
+ if (string.isEmpty())
+ return true;
+
+ maybeEatWhitespace();
+
+ const int stringLength = string.length();
+ const int stringStart = m_position - (stringLength - 1);
+
+ if (stringStart < 0)
+ return false;
+
+ if (m_assistInterface->textAt(stringStart, stringLength) == string) {
+ m_position = stringStart - 1;
+ return true;
+ }
+
+ return false;
+ }
+
+ void maybeEatWhitespace()
+ {
+ while (isPositionValid() && m_assistInterface->characterAt(m_position).isSpace())
+ --m_position;
+ }
+
+private:
+ int m_position;
+ const CppCompletionAssistInterface * const m_assistInterface;
+};
+
+bool canCompleteConnectSignalAt2ndArgument(const CppCompletionAssistInterface *assistInterface,
+ int startOfExpression)
+{
+ BackwardsEater eater(assistInterface, startOfExpression);
+
+ return eater.isPositionValid()
+ && eater.eatExpressionCommaAmpersand()
+ && eater.eatConnectOpenParenthesis();
+}
+
+bool canCompleteConnectSignalAt4thArgument(const CppCompletionAssistInterface *assistInterface,
+ int startPosition)
+{
+ BackwardsEater eater(assistInterface, startPosition);
+
+ return eater.isPositionValid()
+ && eater.eatExpressionCommaAmpersand()
+ && eater.eatConnectOpenParenthesisExpressionCommaAmpersandExpressionComma();
+}
+
+bool canCompleteClassNameAt2ndOr4thConnectArgument(
+ const CppCompletionAssistInterface *assistInterface,
+ int startPosition)
+{
+ BackwardsEater eater(assistInterface, startPosition);
+
+ if (!eater.isPositionValid())
+ return false;
+
+ return eater.eatConnectOpenParenthesis()
+ || eater.eatConnectOpenParenthesisExpressionCommaAmpersandExpressionComma();
+}
+
+ClassOrNamespace *classOrNamespaceFromLookupItem(const LookupItem &lookupItem,
+ const LookupContext &context)
+{
+ const Name *name = nullptr;
+
+ if (Symbol *d = lookupItem.declaration()) {
+ if (Class *k = d->asClass())
+ name = k->name();
+ }
+
+ if (!name) {
+ FullySpecifiedType type = lookupItem.type().simplified();
+
+ if (PointerType *pointerType = type->asPointerType())
+ type = pointerType->elementType().simplified();
+ else
+ return nullptr; // not a pointer or a reference to a pointer.
+
+ NamedType *namedType = type->asNamedType();
+ if (!namedType) // not a class name.
+ return nullptr;
+
+ name = namedType->name();
+ }
+
+ return name ? context.lookupType(name, lookupItem.scope()) : nullptr;
+}
+
+Class *classFromLookupItem(const LookupItem &lookupItem, const LookupContext &context)
+{
+ ClassOrNamespace *b = classOrNamespaceFromLookupItem(lookupItem, context);
+ if (!b)
+ return nullptr;
+
+ foreach (Symbol *s, b->symbols()) {
+ if (Class *klass = s->asClass())
+ return klass;
+ }
+ return nullptr;
+}
+
+const Name *minimalName(Symbol *symbol, Scope *targetScope, const LookupContext &context)
+{
+ ClassOrNamespace *target = context.lookupType(targetScope);
+ if (!target)
+ target = context.globalNamespace();
+ return LookupContext::minimalName(symbol, target, context.bindings()->control().data());
+}
+
+} // Anonymous
+
+// ------------------------------------
+// InternalCppCompletionAssistProcessor
+// ------------------------------------
+InternalCppCompletionAssistProcessor::InternalCppCompletionAssistProcessor()
+ : m_model(new CppAssistProposalModel)
+{
+}
+
+InternalCppCompletionAssistProcessor::~InternalCppCompletionAssistProcessor() = default;
+
+IAssistProposal * InternalCppCompletionAssistProcessor::perform(const AssistInterface *interface)
+{
+ m_interface.reset(static_cast<const CppCompletionAssistInterface *>(interface));
+
+ if (interface->reason() != ExplicitlyInvoked && !accepts())
+ return nullptr;
+
+ int index = startCompletionHelper();
+ if (index != -1) {
+ if (m_hintProposal)
+ return m_hintProposal;
+
+ return createContentProposal();
+ }
+
+ return nullptr;
+}
+
+bool InternalCppCompletionAssistProcessor::accepts() const
+{
+ const int pos = m_interface->position();
+ unsigned token = T_EOF_SYMBOL;
+
+ const int start = startOfOperator(pos, &token, /*want function call=*/ true);
+ if (start != pos) {
+ if (token == T_POUND) {
+ const int column = pos - m_interface->textDocument()->findBlock(start).position();
+ if (column != 1)
+ return false;
+ }
+
+ return true;
+ } else {
+ // Trigger completion after n characters of a name have been typed, when not editing an existing name
+ QChar characterUnderCursor = m_interface->characterAt(pos);
+
+ if (!isValidIdentifierChar(characterUnderCursor)) {
+ const int startOfName = findStartOfName(pos);
+ if (pos - startOfName >= TextEditorSettings::completionSettings().m_characterThreshold) {
+ const QChar firstCharacter = m_interface->characterAt(startOfName);
+ if (isValidFirstIdentifierChar(firstCharacter)) {
+ // Finally check that we're not inside a comment or string (code copied from startOfOperator)
+ QTextCursor tc(m_interface->textDocument());
+ tc.setPosition(pos);
+
+ SimpleLexer tokenize;
+ tokenize.setLanguageFeatures(m_interface->languageFeatures());
+ tokenize.setSkipComments(false);
+
+ const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
+ const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1));
+ const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
+
+ if (!tk.isComment() && !tk.isLiteral()) {
+ return true;
+ } else if (tk.isLiteral()
+ && tokens.size() == 3
+ && tokens.at(0).kind() == T_POUND
+ && tokens.at(1).kind() == T_IDENTIFIER) {
+ const QString &line = tc.block().text();
+ const Token &idToken = tokens.at(1);
+ QStringView identifier = idToken.utf16charsEnd() > line.size()
+ ? QStringView(line).mid(
+ idToken.utf16charsBegin())
+ : QStringView(line)
+ .mid(idToken.utf16charsBegin(),
+ idToken.utf16chars());
+ if (identifier == QLatin1String("include")
+ || identifier == QLatin1String("include_next")
+ || (m_interface->languageFeatures().objCEnabled && identifier == QLatin1String("import"))) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+IAssistProposal *InternalCppCompletionAssistProcessor::createContentProposal()
+{
+ // Duplicates are kept only if they are snippets.
+ QSet<QString> processed;
+ auto it = m_completions.begin();
+ while (it != m_completions.end()) {
+ auto item = static_cast<CppAssistProposalItem *>(*it);
+ if (!processed.contains(item->text()) || item->isSnippet()) {
+ ++it;
+ if (!item->isSnippet()) {
+ processed.insert(item->text());
+ if (!item->isOverloaded()) {
+ if (auto symbol = qvariant_cast<Symbol *>(item->data())) {
+ if (Function *funTy = symbol->type()->asFunctionType()) {
+ if (funTy->hasArguments())
+ item->markAsOverloaded();
+ }
+ }
+ }
+ }
+ } else {
+ delete *it;
+ it = m_completions.erase(it);
+ }
+ }
+
+ m_model->loadContent(m_completions);
+ return new CppAssistProposal(m_positionForProposal, m_model);
+}
+
+IAssistProposal *InternalCppCompletionAssistProcessor::createHintProposal(
+ QList<Function *> functionSymbols) const
+{
+ FunctionHintProposalModelPtr model(new CppFunctionHintModel(functionSymbols,
+ m_model->m_typeOfExpression));
+ return new FunctionHintProposal(m_positionForProposal, model);
+}
+
+int InternalCppCompletionAssistProcessor::startOfOperator(int positionInDocument,
+ unsigned *kind,
+ bool wantFunctionCall) const
+{
+ const QChar ch = m_interface->characterAt(positionInDocument - 1);
+ const QChar ch2 = m_interface->characterAt(positionInDocument - 2);
+ const QChar ch3 = m_interface->characterAt(positionInDocument - 3);
+
+ int start = positionInDocument
+ - CppCompletionAssistProvider::activationSequenceChar(ch, ch2, ch3, kind,
+ wantFunctionCall,
+ /*wantQt5SignalSlots*/ true);
+
+ const auto dotAtIncludeCompletionHandler = [this](int &start, unsigned *kind) {
+ start = findStartOfName(start);
+ const QChar ch4 = m_interface->characterAt(start - 1);
+ const QChar ch5 = m_interface->characterAt(start - 2);
+ const QChar ch6 = m_interface->characterAt(start - 3);
+ start = start - CppCompletionAssistProvider::activationSequenceChar(
+ ch4, ch5, ch6, kind, false, false);
+ };
+
+ CppCompletionAssistProcessor::startOfOperator(m_interface->textDocument(),
+ positionInDocument,
+ kind,
+ start,
+ m_interface->languageFeatures(),
+ /*adjustForQt5SignalSlotCompletion=*/ true,
+ dotAtIncludeCompletionHandler);
+ return start;
+}
+
+int InternalCppCompletionAssistProcessor::findStartOfName(int pos) const
+{
+ if (pos == -1)
+ pos = m_interface->position();
+ QChar chr;
+
+ // Skip to the start of a name
+ do {
+ chr = m_interface->characterAt(--pos);
+ } while (isValidIdentifierChar(chr));
+
+ return pos + 1;
+}
+
+int InternalCppCompletionAssistProcessor::startCompletionHelper()
+{
+ if (m_interface->languageFeatures().objCEnabled) {
+ if (tryObjCCompletion())
+ return m_positionForProposal;
+ }
+
+ const int startOfName = findStartOfName();
+ m_positionForProposal = startOfName;
+ m_model->m_completionOperator = T_EOF_SYMBOL;
+
+ int endOfOperator = m_positionForProposal;
+
+ // Skip whitespace preceding this position
+ while (m_interface->characterAt(endOfOperator - 1).isSpace())
+ --endOfOperator;
+
+ int endOfExpression = startOfOperator(endOfOperator,
+ &m_model->m_completionOperator,
+ /*want function call =*/ true);
+
+ if (m_model->m_completionOperator == T_DOXY_COMMENT) {
+ for (int i = 1; i < T_DOXY_LAST_TAG; ++i)
+ addCompletionItem(QString::fromLatin1(doxygenTagSpell(i)), Icons::keywordIcon());
+ return m_positionForProposal;
+ }
+
+ // Pre-processor completion
+ if (m_model->m_completionOperator == T_POUND) {
+ completePreprocessor();
+ m_positionForProposal = startOfName;
+ return m_positionForProposal;
+ }
+
+ // Include completion
+ if (m_model->m_completionOperator == T_STRING_LITERAL
+ || m_model->m_completionOperator == T_ANGLE_STRING_LITERAL
+ || m_model->m_completionOperator == T_SLASH) {
+
+ QTextCursor c(m_interface->textDocument());
+ c.setPosition(endOfExpression);
+ if (completeInclude(c))
+ m_positionForProposal = endOfExpression + 1;
+ return m_positionForProposal;
+ }
+
+ ExpressionUnderCursor expressionUnderCursor(m_interface->languageFeatures());
+ QTextCursor tc(m_interface->textDocument());
+
+ if (m_model->m_completionOperator == T_COMMA) {
+ tc.setPosition(endOfExpression);
+ const int start = expressionUnderCursor.startOfFunctionCall(tc);
+ if (start == -1) {
+ m_model->m_completionOperator = T_EOF_SYMBOL;
+ return -1;
+ }
+
+ endOfExpression = start;
+ m_positionForProposal = start + 1;
+ m_model->m_completionOperator = T_LPAREN;
+ }
+
+ QString expression;
+ int startOfExpression = m_interface->position();
+ tc.setPosition(endOfExpression);
+
+ if (m_model->m_completionOperator) {
+ expression = expressionUnderCursor(tc);
+ startOfExpression = endOfExpression - expression.length();
+
+ if (m_model->m_completionOperator == T_AMPER) {
+ // We expect 'expression' to be either "sender" or "receiver" in
+ // "connect(sender, &" or
+ // "connect(otherSender, &Foo::signal1, receiver, &"
+ const int beforeExpression = startOfExpression - 1;
+ if (canCompleteClassNameAt2ndOr4thConnectArgument(m_interface.data(),
+ beforeExpression)) {
+ m_model->m_completionOperator = CompleteQt5SignalOrSlotClassNameTrigger;
+ } else { // Ensure global completion
+ startOfExpression = endOfExpression = m_positionForProposal;
+ expression.clear();
+ m_model->m_completionOperator = T_EOF_SYMBOL;
+ }
+ } else if (m_model->m_completionOperator == T_COLON_COLON) {
+ // We expect 'expression' to be "Foo" in
+ // "connect(sender, &Foo::" or
+ // "connect(sender, &Bar::signal1, receiver, &Foo::"
+ const int beforeExpression = startOfExpression - 1;
+ if (canCompleteConnectSignalAt2ndArgument(m_interface.data(), beforeExpression))
+ m_model->m_completionOperator = CompleteQt5SignalTrigger;
+ else if (canCompleteConnectSignalAt4thArgument(m_interface.data(), beforeExpression))
+ m_model->m_completionOperator = CompleteQt5SlotTrigger;
+ } else if (m_model->m_completionOperator == T_LPAREN) {
+ if (expression.endsWith(QLatin1String("SIGNAL"))) {
+ m_model->m_completionOperator = T_SIGNAL;
+ } else if (expression.endsWith(QLatin1String("SLOT"))) {
+ m_model->m_completionOperator = T_SLOT;
+ } else if (m_interface->position() != endOfOperator) {
+ // We don't want a function completion when the cursor isn't at the opening brace
+ expression.clear();
+ m_model->m_completionOperator = T_EOF_SYMBOL;
+ m_positionForProposal = startOfName;
+ startOfExpression = m_interface->position();
+ }
+ }
+ } else if (expression.isEmpty()) {
+ while (startOfExpression > 0 && m_interface->characterAt(startOfExpression).isSpace())
+ --startOfExpression;
+ }
+
+ int line = 0, column = 0;
+ Utils::Text::convertPosition(m_interface->textDocument(), startOfExpression, &line, &column);
+ const QString fileName = m_interface->filePath().toString();
+ return startCompletionInternal(fileName, line, column - 1, expression, endOfExpression);
+}
+
+bool InternalCppCompletionAssistProcessor::tryObjCCompletion()
+{
+ int end = m_interface->position();
+ while (m_interface->characterAt(end).isSpace())
+ ++end;
+ if (m_interface->characterAt(end) != QLatin1Char(']'))
+ return false;
+
+ QTextCursor tc(m_interface->textDocument());
+ tc.setPosition(end);
+ BackwardsScanner tokens(tc, m_interface->languageFeatures());
+ if (tokens[tokens.startToken() - 1].isNot(T_RBRACKET))
+ return false;
+
+ const int start = tokens.startOfMatchingBrace(tokens.startToken());
+ if (start == tokens.startToken())
+ return false;
+
+ const int startPos = tokens[start].bytesBegin() + tokens.startPosition();
+ const QString expr = m_interface->textAt(startPos, m_interface->position() - startPos);
+
+ Document::Ptr thisDocument = m_interface->snapshot().document(m_interface->filePath());
+ if (!thisDocument)
+ return false;
+
+ m_model->m_typeOfExpression->init(thisDocument, m_interface->snapshot());
+
+ int line = 0, column = 0;
+ Utils::Text::convertPosition(m_interface->textDocument(), m_interface->position(), &line,
+ &column);
+ Scope *scope = thisDocument->scopeAt(line, column - 1);
+ if (!scope)
+ return false;
+
+ const QList<LookupItem> items = (*m_model->m_typeOfExpression)(expr.toUtf8(), scope);
+ LookupContext lookupContext(thisDocument, m_interface->snapshot());
+
+ foreach (const LookupItem &item, items) {
+ FullySpecifiedType ty = item.type().simplified();
+ if (ty->isPointerType()) {
+ ty = ty->asPointerType()->elementType().simplified();
+
+ if (NamedType *namedTy = ty->asNamedType()) {
+ ClassOrNamespace *binding = lookupContext.lookupType(namedTy->name(), item.scope());
+ completeObjCMsgSend(binding, false);
+ }
+ } else {
+ if (ObjCClass *clazz = ty->asObjCClassType()) {
+ ClassOrNamespace *binding = lookupContext.lookupType(clazz->name(), item.scope());
+ completeObjCMsgSend(binding, true);
+ }
+ }
+ }
+
+ if (m_completions.isEmpty())
+ return false;
+
+ m_positionForProposal = m_interface->position();
+ return true;
+}
+
+namespace {
+enum CompletionOrder {
+ // default order is 0
+ FunctionArgumentsOrder = 2,
+ FunctionLocalsOrder = 2, // includes local types
+ PublicClassMemberOrder = 1,
+ InjectedClassNameOrder = -1,
+ MacrosOrder = -2,
+ KeywordsOrder = -2
+};
+}
+
+void InternalCppCompletionAssistProcessor::addCompletionItem(const QString &text,
+ const QIcon &icon,
+ int order,
+ const QVariant &data)
+{
+ AssistProposalItem *item = new CppAssistProposalItem;
+ item->setText(text);
+ item->setIcon(icon);
+ item->setOrder(order);
+ item->setData(data);
+ m_completions.append(item);
+}
+
+void InternalCppCompletionAssistProcessor::addCompletionItem(Symbol *symbol, int order)
+{
+ ConvertToCompletionItem toCompletionItem;
+ AssistProposalItem *item = toCompletionItem(symbol);
+ if (item) {
+ item->setIcon(Icons::iconForSymbol(symbol));
+ item->setOrder(order);
+ m_completions.append(item);
+ }
+}
+
+void InternalCppCompletionAssistProcessor::completeObjCMsgSend(ClassOrNamespace *binding,
+ bool staticClassAccess)
+{
+ QList<Scope*> memberScopes;
+ foreach (Symbol *s, binding->symbols()) {
+ if (ObjCClass *c = s->asObjCClass())
+ memberScopes.append(c);
+ }
+
+ foreach (Scope *scope, memberScopes) {
+ for (int i = 0; i < scope->memberCount(); ++i) {
+ Symbol *symbol = scope->memberAt(i);
+
+ if (ObjCMethod *method = symbol->type()->asObjCMethodType()) {
+ if (method->isStatic() == staticClassAccess) {
+ Overview oo;
+ const SelectorNameId *selectorName =
+ method->name()->asSelectorNameId();
+ QString text;
+ QString data;
+ if (selectorName->hasArguments()) {
+ for (int i = 0; i < selectorName->nameCount(); ++i) {
+ if (i > 0)
+ text += QLatin1Char(' ');
+ Symbol *arg = method->argumentAt(i);
+ text += QString::fromUtf8(selectorName->nameAt(i)->identifier()->chars());
+ text += QLatin1Char(':');
+ text += Snippet::kVariableDelimiter;
+ text += QLatin1Char('(');
+ text += oo.prettyType(arg->type());
+ text += QLatin1Char(')');
+ text += oo.prettyName(arg->name());
+ text += Snippet::kVariableDelimiter;
+ }
+ } else {
+ text = QString::fromUtf8(selectorName->identifier()->chars());
+ }
+ data = text;
+
+ if (!text.isEmpty())
+ addCompletionItem(text, QIcon(), 0, QVariant::fromValue(data));
+ }
+ }
+ }
+ }
+}
+
+bool InternalCppCompletionAssistProcessor::completeInclude(const QTextCursor &cursor)
+{
+ QString directoryPrefix;
+ if (m_model->m_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_model->m_completionOperator = T_ANGLE_STRING_LITERAL;
+ } else {
+ m_model->m_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();
+ const auto currentFilePath = ProjectExplorer::HeaderPath::makeUser(
+ m_interface->filePath().toFileInfo().path());
+ if (!headerPaths.contains(currentFilePath))
+ headerPaths.append(currentFilePath);
+
+ const QStringList suffixes = Utils::mimeTypeForName(QLatin1String("text/x-c++hdr")).suffixes();
+
+ foreach (const ProjectExplorer::HeaderPath &headerPath, headerPaths) {
+ QString realPath = headerPath.path;
+ if (!directoryPrefix.isEmpty()) {
+ realPath += QLatin1Char('/');
+ realPath += directoryPrefix;
+ if (headerPath.type == ProjectExplorer::HeaderPathType::Framework)
+ realPath += QLatin1String(".framework/Headers");
+ }
+ completeInclude(realPath, suffixes);
+ }
+
+ return !m_completions.isEmpty();
+}
+
+void InternalCppCompletionAssistProcessor::completeInclude(const QString &realPath,
+ const QStringList &suffixes)
+{
+ QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
+ while (i.hasNext()) {
+ const QString fileName = i.next();
+ const QFileInfo fileInfo = i.fileInfo();
+ const QString suffix = fileInfo.suffix();
+ if (suffix.isEmpty() || suffixes.contains(suffix)) {
+ QString text = fileName.mid(realPath.length() + 1);
+ if (fileInfo.isDir())
+ text += QLatin1Char('/');
+ addCompletionItem(text, Icons::keywordIcon());
+ }
+ }
+}
+
+void InternalCppCompletionAssistProcessor::completePreprocessor()
+{
+ foreach (const QString &preprocessorCompletion, m_preprocessorCompletions)
+ addCompletionItem(preprocessorCompletion);
+
+ if (objcKeywordsWanted())
+ addCompletionItem(QLatin1String("import"));
+}
+
+bool InternalCppCompletionAssistProcessor::objcKeywordsWanted() const
+{
+ if (!m_interface->languageFeatures().objCEnabled)
+ return false;
+
+ const Utils::MimeType mt = Utils::mimeTypeForFile(m_interface->filePath());
+ return mt.matchesName(QLatin1String(CppEditor::Constants::OBJECTIVE_C_SOURCE_MIMETYPE))
+ || mt.matchesName(QLatin1String(CppEditor::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE));
+}
+
+int InternalCppCompletionAssistProcessor::startCompletionInternal(const QString &fileName,
+ int line,
+ int positionInBlock,
+ const QString &expr,
+ int endOfExpression)
+{
+ QString expression = expr.trimmed();
+
+ Document::Ptr thisDocument = m_interface->snapshot().document(fileName);
+ if (!thisDocument)
+ return -1;
+
+ m_model->m_typeOfExpression->init(thisDocument, m_interface->snapshot());
+
+ Scope *scope = thisDocument->scopeAt(line, positionInBlock);
+ QTC_ASSERT(scope, return -1);
+
+ if (expression.isEmpty()) {
+ if (m_model->m_completionOperator == T_EOF_SYMBOL || m_model->m_completionOperator == T_COLON_COLON) {
+ (void) (*m_model->m_typeOfExpression)(expression.toUtf8(), scope);
+ return globalCompletion(scope) ? m_positionForProposal : -1;
+ }
+
+ if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) {
+ // Apply signal/slot completion on 'this'
+ expression = QLatin1String("this");
+ }
+ }
+
+ QByteArray utf8Exp = expression.toUtf8();
+ QList<LookupItem> results =
+ (*m_model->m_typeOfExpression)(utf8Exp, scope, TypeOfExpression::Preprocess);
+
+ if (results.isEmpty()) {
+ if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) {
+ if (!(expression.isEmpty() || expression == QLatin1String("this"))) {
+ expression = QLatin1String("this");
+ results = (*m_model->m_typeOfExpression)(utf8Exp, scope);
+ }
+
+ if (results.isEmpty())
+ return -1;
+
+ } else if (m_model->m_completionOperator == T_LPAREN) {
+ // Find the expression that precedes the current name
+ int index = endOfExpression;
+ while (m_interface->characterAt(index - 1).isSpace())
+ --index;
+ index = findStartOfName(index);
+
+ QTextCursor tc(m_interface->textDocument());
+ tc.setPosition(index);
+
+ ExpressionUnderCursor expressionUnderCursor(m_interface->languageFeatures());
+ const QString baseExpression = expressionUnderCursor(tc);
+
+ // Resolve the type of this expression
+ const QList<LookupItem> results =
+ (*m_model->m_typeOfExpression)(baseExpression.toUtf8(), scope,
+ TypeOfExpression::Preprocess);
+
+ // If it's a class, add completions for the constructors
+ foreach (const LookupItem &result, results) {
+ if (result.type()->isClassType()) {
+ if (completeConstructorOrFunction(results, endOfExpression, true))
+ return m_positionForProposal;
+
+ break;
+ }
+ }
+ return -1;
+
+ } else if (m_model->m_completionOperator == CompleteQt5SignalOrSlotClassNameTrigger) {
+ // Fallback to global completion if we could not lookup sender/receiver object.
+ return globalCompletion(scope) ? m_positionForProposal : -1;
+
+ } else {
+ return -1; // nothing to do.
+ }
+ }
+
+ switch (m_model->m_completionOperator) {
+ case T_LPAREN:
+ if (completeConstructorOrFunction(results, endOfExpression, false))
+ return m_positionForProposal;
+ break;
+
+ case T_DOT:
+ case T_ARROW:
+ if (completeMember(results))
+ return m_positionForProposal;
+ break;
+
+ case T_COLON_COLON:
+ if (completeScope(results))
+ return m_positionForProposal;
+ break;
+
+ case T_SIGNAL:
+ if (completeQtMethod(results, CompleteQt4Signals))
+ return m_positionForProposal;
+ break;
+
+ case T_SLOT:
+ if (completeQtMethod(results, CompleteQt4Slots))
+ return m_positionForProposal;
+ break;
+
+ case CompleteQt5SignalOrSlotClassNameTrigger:
+ if (completeQtMethodClassName(results, scope) || globalCompletion(scope))
+ return m_positionForProposal;
+ break;
+
+ case CompleteQt5SignalTrigger:
+ // Fallback to scope completion if "X::" is a namespace and not a class.
+ if (completeQtMethod(results, CompleteQt5Signals) || completeScope(results))
+ return m_positionForProposal;
+ break;
+
+ case CompleteQt5SlotTrigger:
+ // Fallback to scope completion if "X::" is a namespace and not a class.
+ if (completeQtMethod(results, CompleteQt5Slots) || completeScope(results))
+ return m_positionForProposal;
+ break;
+
+ default:
+ break;
+ } // end of switch
+
+ // nothing to do.
+ return -1;
+}
+
+bool InternalCppCompletionAssistProcessor::globalCompletion(Scope *currentScope)
+{
+ const LookupContext &context = m_model->m_typeOfExpression->context();
+
+ if (m_model->m_completionOperator == T_COLON_COLON) {
+ completeNamespace(context.globalNamespace());
+ return !m_completions.isEmpty();
+ }
+
+ QList<ClassOrNamespace *> usingBindings;
+ ClassOrNamespace *currentBinding = nullptr;
+
+ for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) {
+ if (Block *block = scope->asBlock()) {
+ if (ClassOrNamespace *binding = context.lookupType(scope)) {
+ for (int i = 0; i < scope->memberCount(); ++i) {
+ Symbol *member = scope->memberAt(i);
+ if (member->isEnum()) {
+ if (ClassOrNamespace *b = binding->findBlock(block))
+ completeNamespace(b);
+ }
+ if (!member->name())
+ continue;
+ if (UsingNamespaceDirective *u = member->asUsingNamespaceDirective()) {
+ if (ClassOrNamespace *b = binding->lookupType(u->name()))
+ usingBindings.append(b);
+ } else if (Class *c = member->asClass()) {
+ if (c->name()->isAnonymousNameId()) {
+ if (ClassOrNamespace *b = binding->findBlock(block))
+ completeClass(b);
+ }
+ }
+ }
+ }
+ } else if (scope->isFunction() || scope->isClass() || scope->isNamespace()) {
+ currentBinding = context.lookupType(scope);
+ break;
+ }
+ }
+
+ for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) {
+ if (scope->isBlock()) {
+ for (int i = 0; i < scope->memberCount(); ++i)
+ addCompletionItem(scope->memberAt(i), FunctionLocalsOrder);
+ } else if (Function *fun = scope->asFunction()) {
+ for (int i = 0, argc = fun->argumentCount(); i < argc; ++i)
+ addCompletionItem(fun->argumentAt(i), FunctionArgumentsOrder);
+ } else if (Template *templ = scope->asTemplate()) {
+ for (int i = 0, argc = templ->templateParameterCount(); i < argc; ++i)
+ addCompletionItem(templ->templateParameterAt(i), FunctionArgumentsOrder);
+ break;
+ }
+ }
+
+ QSet<ClassOrNamespace *> processed;
+ for (; currentBinding; currentBinding = currentBinding->parent()) {
+ if (processed.contains(currentBinding))
+ break;
+ processed.insert(currentBinding);
+
+ foreach (ClassOrNamespace* u, currentBinding->usings())
+ usingBindings.append(u);
+
+ const QList<Symbol *> symbols = currentBinding->symbols();
+
+ if (!symbols.isEmpty()) {
+ if (symbols.first()->isClass())
+ completeClass(currentBinding);
+ else
+ completeNamespace(currentBinding);
+ }
+ }
+
+ foreach (ClassOrNamespace *b, usingBindings)
+ completeNamespace(b);
+
+ addKeywords();
+ addMacros(CppModelManager::configurationFileName(), context.snapshot());
+ addMacros(context.thisDocument()->fileName(), context.snapshot());
+ addSnippets();
+ return !m_completions.isEmpty();
+}
+
+void InternalCppCompletionAssistProcessor::addKeywordCompletionItem(const QString &text)
+{
+ auto item = new CppAssistProposalItem;
+ item->setText(text);
+ item->setIcon(Icons::keywordIcon());
+ item->setOrder(KeywordsOrder);
+ item->setIsKeyword(true);
+ m_completions.append(item);
+}
+
+bool InternalCppCompletionAssistProcessor::completeMember(const QList<LookupItem> &baseResults)
+{
+ const LookupContext &context = m_model->m_typeOfExpression->context();
+
+ if (baseResults.isEmpty())
+ return false;
+
+ ResolveExpression resolveExpression(context);
+
+ bool *replaceDotForArrow = nullptr;
+ if (!m_interface->languageFeatures().objCEnabled)
+ replaceDotForArrow = &m_model->m_replaceDotForArrow;
+
+ if (ClassOrNamespace *binding =
+ resolveExpression.baseExpression(baseResults,
+ m_model->m_completionOperator,
+ replaceDotForArrow)) {
+ if (binding)
+ completeClass(binding, /*static lookup = */ true);
+
+ return !m_completions.isEmpty();
+ }
+
+ return false;
+}
+
+bool InternalCppCompletionAssistProcessor::completeScope(const QList<LookupItem> &results)
+{
+ const LookupContext &context = m_model->m_typeOfExpression->context();
+ if (results.isEmpty())
+ return false;
+
+ foreach (const LookupItem &result, results) {
+ FullySpecifiedType ty = result.type();
+ Scope *scope = result.scope();
+
+ if (NamedType *namedTy = ty->asNamedType()) {
+ if (ClassOrNamespace *b = context.lookupType(namedTy->name(), scope)) {
+ completeClass(b);
+ break;
+ }
+
+ } else if (Class *classTy = ty->asClassType()) {
+ if (ClassOrNamespace *b = context.lookupType(classTy)) {
+ completeClass(b);
+ break;
+ }
+
+ // it can be class defined inside a block
+ if (classTy->enclosingScope()->isBlock()) {
+ if (ClassOrNamespace *b = context.lookupType(classTy->name(), classTy->enclosingScope())) {
+ completeClass(b);
+ break;
+ }
+ }
+
+ } else if (Namespace *nsTy = ty->asNamespaceType()) {
+ if (ClassOrNamespace *b = context.lookupType(nsTy)) {
+ completeNamespace(b);
+ break;
+ }
+
+ } else if (Template *templ = ty->asTemplateType()) {
+ if (!result.binding())
+ continue;
+ if (ClassOrNamespace *b = result.binding()->lookupType(templ->name())) {
+ completeClass(b);
+ break;
+ }
+
+ } else if (Enum *e = ty->asEnumType()) {
+ // it can be class defined inside a block
+ if (e->enclosingScope()->isBlock()) {
+ if (ClassOrNamespace *b = context.lookupType(e)) {
+ Block *block = e->enclosingScope()->asBlock();
+ if (ClassOrNamespace *bb = b->findBlock(block)) {
+ completeNamespace(bb);
+ break;
+ }
+ }
+ }
+
+ if (ClassOrNamespace *b = context.lookupType(e)) {
+ completeNamespace(b);
+ break;
+ }
+
+ }
+ }
+
+ return !m_completions.isEmpty();
+}
+
+void InternalCppCompletionAssistProcessor::completeNamespace(ClassOrNamespace *b)
+{
+ QSet<ClassOrNamespace *> bindingsVisited;
+ QList<ClassOrNamespace *> bindingsToVisit;
+ bindingsToVisit.append(b);
+
+ while (!bindingsToVisit.isEmpty()) {
+ ClassOrNamespace *binding = bindingsToVisit.takeFirst();
+ if (!binding || bindingsVisited.contains(binding))
+ continue;
+
+ bindingsVisited.insert(binding);
+ bindingsToVisit += binding->usings();
+
+ QList<Scope *> scopesToVisit;
+ QSet<Scope *> scopesVisited;
+
+ foreach (Symbol *bb, binding->symbols()) {
+ if (Scope *scope = bb->asScope())
+ scopesToVisit.append(scope);
+ }
+
+ foreach (Enum *e, binding->unscopedEnums())
+ scopesToVisit.append(e);
+
+ while (!scopesToVisit.isEmpty()) {
+ Scope *scope = scopesToVisit.takeFirst();
+ if (!scope || scopesVisited.contains(scope))
+ continue;
+
+ scopesVisited.insert(scope);
+
+ for (Scope::iterator it = scope->memberBegin(); it != scope->memberEnd(); ++it) {
+ Symbol *member = *it;
+ addCompletionItem(member);
+ }
+ }
+ }
+}
+
+void InternalCppCompletionAssistProcessor::completeClass(ClassOrNamespace *b, bool staticLookup)
+{
+ QSet<ClassOrNamespace *> bindingsVisited;
+ QList<ClassOrNamespace *> bindingsToVisit;
+ bindingsToVisit.append(b);
+
+ while (!bindingsToVisit.isEmpty()) {
+ ClassOrNamespace *binding = bindingsToVisit.takeFirst();
+ if (!binding || bindingsVisited.contains(binding))
+ continue;
+
+ bindingsVisited.insert(binding);
+ bindingsToVisit += binding->usings();
+
+ QList<Scope *> scopesToVisit;
+ QSet<Scope *> scopesVisited;
+
+ foreach (Symbol *bb, binding->symbols()) {
+ if (Class *k = bb->asClass())
+ scopesToVisit.append(k);
+ else if (Block *b = bb->asBlock())
+ scopesToVisit.append(b);
+ }
+
+ foreach (Enum *e, binding->unscopedEnums())
+ scopesToVisit.append(e);
+
+ while (!scopesToVisit.isEmpty()) {
+ Scope *scope = scopesToVisit.takeFirst();
+ if (!scope || scopesVisited.contains(scope))
+ continue;
+
+ scopesVisited.insert(scope);
+
+ if (staticLookup)
+ addCompletionItem(scope, InjectedClassNameOrder); // add a completion item for the injected class name.
+
+ addClassMembersToCompletion(scope, staticLookup);
+ }
+ }
+}
+
+void InternalCppCompletionAssistProcessor::addClassMembersToCompletion(Scope *scope,
+ bool staticLookup)
+{
+ if (!scope)
+ return;
+
+ std::set<Class *> nestedAnonymouses;
+
+ for (Scope::iterator it = scope->memberBegin(); it != scope->memberEnd(); ++it) {
+ Symbol *member = *it;
+ if (member->isFriend()
+ || member->isQtPropertyDeclaration()
+ || member->isQtEnum()) {
+ continue;
+ } else if (!staticLookup && (member->isTypedef() ||
+ member->isEnum() ||
+ member->isClass())) {
+ continue;
+ } else if (member->isClass() && member->name()->isAnonymousNameId()) {
+ nestedAnonymouses.insert(member->asClass());
+ } else if (member->isDeclaration()) {
+ Class *declTypeAsClass = member->asDeclaration()->type()->asClassType();
+ if (declTypeAsClass && declTypeAsClass->name()->isAnonymousNameId())
+ nestedAnonymouses.erase(declTypeAsClass);
+ }
+
+ if (member->isPublic())
+ addCompletionItem(member, PublicClassMemberOrder);
+ else
+ addCompletionItem(member);
+ }
+ for (Class *klass : nestedAnonymouses)
+ addClassMembersToCompletion(klass, staticLookup);
+}
+
+bool InternalCppCompletionAssistProcessor::completeQtMethod(const QList<LookupItem> &results,
+ CompleteQtMethodMode type)
+{
+ if (results.isEmpty())
+ return false;
+
+ const LookupContext &context = m_model->m_typeOfExpression->context();
+
+ ConvertToCompletionItem toCompletionItem;
+ Overview o;
+ o.showReturnTypes = false;
+ o.showArgumentNames = false;
+ o.showFunctionSignatures = true;
+
+ QSet<QString> signatures;
+ foreach (const LookupItem &lookupItem, results) {
+ ClassOrNamespace *b = classOrNamespaceFromLookupItem(lookupItem, context);
+ if (!b)
+ continue;
+
+ QList<ClassOrNamespace *>todo;
+ QSet<ClassOrNamespace *> processed;
+ QList<Scope *> scopes;
+ todo.append(b);
+ while (!todo.isEmpty()) {
+ ClassOrNamespace *binding = todo.takeLast();
+ if (!processed.contains(binding)) {
+ processed.insert(binding);
+
+ foreach (Symbol *s, binding->symbols())
+ if (Class *clazz = s->asClass())
+ scopes.append(clazz);
+
+ todo.append(binding->usings());
+ }
+ }
+
+ const bool wantSignals = type == CompleteQt4Signals || type == CompleteQt5Signals;
+ const bool wantQt5SignalOrSlot = type == CompleteQt5Signals || type == CompleteQt5Slots;
+ foreach (Scope *scope, scopes) {
+ Class *klass = scope->asClass();
+ if (!klass)
+ continue;
+
+ for (int i = 0; i < scope->memberCount(); ++i) {
+ Symbol *member = scope->memberAt(i);
+ Function *fun = member->type()->asFunctionType();
+ if (!fun || fun->isGenerated())
+ continue;
+ if (wantSignals && !fun->isSignal())
+ continue;
+ else if (!wantSignals && type == CompleteQt4Slots && !fun->isSlot())
+ continue;
+
+ int count = fun->argumentCount();
+ while (true) {
+ const QString completionText = wantQt5SignalOrSlot
+ ? createQt5SignalOrSlot(fun, o)
+ : createQt4SignalOrSlot(fun, o);
+
+ if (!signatures.contains(completionText)) {
+ AssistProposalItem *ci = toCompletionItem(fun);
+ if (!ci)
+ break;
+ signatures.insert(completionText);
+ ci->setText(completionText); // fix the completion item.
+ ci->setIcon(Icons::iconForSymbol(fun));
+ if (wantQt5SignalOrSlot && fun->isSlot())
+ ci->setOrder(1);
+ m_completions.append(ci);
+ }
+
+ if (count && fun->argumentAt(count - 1)->asArgument()->hasInitializer())
+ --count;
+ else
+ break;
+ }
+ }
+ }
+ }
+
+ return !m_completions.isEmpty();
+}
+
+bool InternalCppCompletionAssistProcessor::completeQtMethodClassName(
+ const QList<LookupItem> &results, Scope *cursorScope)
+{
+ QTC_ASSERT(cursorScope, return false);
+
+ if (results.isEmpty())
+ return false;
+
+ const LookupContext &context = m_model->m_typeOfExpression->context();
+ const QIcon classIcon = Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Class);
+ Overview overview;
+
+ foreach (const LookupItem &lookupItem, results) {
+ Class *klass = classFromLookupItem(lookupItem, context);
+ if (!klass)
+ continue;
+ const Name *name = minimalName(klass, cursorScope, context);
+ QTC_ASSERT(name, continue);
+
+ addCompletionItem(overview.prettyName(name), classIcon);
+ break;
+ }
+
+ return !m_completions.isEmpty();
+}
+
+void InternalCppCompletionAssistProcessor::addKeywords()
+{
+ int keywordLimit = T_FIRST_OBJC_AT_KEYWORD;
+ if (objcKeywordsWanted())
+ keywordLimit = T_LAST_OBJC_AT_KEYWORD + 1;
+
+ // keyword completion items.
+ for (int i = T_FIRST_KEYWORD; i < keywordLimit; ++i)
+ addKeywordCompletionItem(QLatin1String(Token::name(i)));
+
+ // primitive type completion items.
+ for (int i = T_FIRST_PRIMITIVE; i <= T_LAST_PRIMITIVE; ++i)
+ addKeywordCompletionItem(QLatin1String(Token::name(i)));
+
+ // "Identifiers with special meaning"
+ if (m_interface->languageFeatures().cxx11Enabled) {
+ addKeywordCompletionItem(QLatin1String("override"));
+ addKeywordCompletionItem(QLatin1String("final"));
+ }
+}
+
+void InternalCppCompletionAssistProcessor::addMacros(const QString &fileName,
+ const Snapshot &snapshot)
+{
+ QSet<QString> processed;
+ QSet<QString> definedMacros;
+
+ addMacros_helper(snapshot, fileName, &processed, &definedMacros);
+
+ foreach (const QString &macroName, definedMacros)
+ addCompletionItem(macroName, Icons::macroIcon(), MacrosOrder);
+}
+
+void InternalCppCompletionAssistProcessor::addMacros_helper(const Snapshot &snapshot,
+ const QString &fileName,
+ QSet<QString> *processed,
+ QSet<QString> *definedMacros)
+{
+ Document::Ptr doc = snapshot.document(fileName);
+
+ if (!doc || processed->contains(doc->fileName()))
+ return;
+
+ processed->insert(doc->fileName());
+
+ foreach (const Document::Include &i, doc->resolvedIncludes())
+ addMacros_helper(snapshot, i.resolvedFileName(), processed, definedMacros);
+
+ foreach (const CPlusPlus::Macro &macro, doc->definedMacros()) {
+ const QString macroName = macro.nameToQString();
+ if (!macro.isHidden())
+ definedMacros->insert(macroName);
+ else
+ definedMacros->remove(macroName);
+ }
+}
+
+bool InternalCppCompletionAssistProcessor::completeConstructorOrFunction(const QList<LookupItem> &results,
+ int endOfExpression,
+ bool toolTipOnly)
+{
+ const LookupContext &context = m_model->m_typeOfExpression->context();
+ QList<Function *> functions;
+
+ foreach (const LookupItem &result, results) {
+ FullySpecifiedType exprTy = result.type().simplified();
+
+ if (Class *klass = asClassOrTemplateClassType(exprTy)) {
+ const Name *className = klass->name();
+ if (!className)
+ continue; // nothing to do for anonymous classes.
+
+ for (int i = 0; i < klass->memberCount(); ++i) {
+ Symbol *member = klass->memberAt(i);
+ const Name *memberName = member->name();
+
+ if (!memberName)
+ continue; // skip anonymous member.
+
+ else if (memberName->isQualifiedNameId())
+ continue; // skip
+
+ if (Function *funTy = member->type()->asFunctionType()) {
+ if (memberName->match(className)) {
+ // it's a ctor.
+ functions.append(funTy);
+ }
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (functions.isEmpty()) {
+ foreach (const LookupItem &result, results) {
+ FullySpecifiedType ty = result.type().simplified();
+
+ if (Function *fun = asFunctionOrTemplateFunctionType(ty)) {
+
+ if (!fun->name()) {
+ continue;
+ } else if (!functions.isEmpty()
+ && enclosingNonTemplateScope(functions.first())
+ != enclosingNonTemplateScope(fun)) {
+ continue; // skip fun, it's an hidden declaration.
+ }
+
+ bool newOverload = true;
+
+ foreach (Function *f, functions) {
+ if (fun->match(f)) {
+ newOverload = false;
+ break;
+ }
+ }
+
+ if (newOverload)
+ functions.append(fun);
+ }
+ }
+ }
+
+ if (functions.isEmpty()) {
+ const Name *functionCallOp = context.bindings()->control()->operatorNameId(OperatorNameId::FunctionCallOp);
+
+ foreach (const LookupItem &result, results) {
+ FullySpecifiedType ty = result.type().simplified();
+ Scope *scope = result.scope();
+
+ if (NamedType *namedTy = ty->asNamedType()) {
+ if (ClassOrNamespace *b = context.lookupType(namedTy->name(), scope)) {
+ foreach (const LookupItem &r, b->lookup(functionCallOp)) {
+ Symbol *overload = r.declaration();
+ FullySpecifiedType overloadTy = overload->type().simplified();
+
+ if (Function *funTy = overloadTy->asFunctionType())
+ functions.append(funTy);
+ }
+ }
+ }
+ }
+ }
+
+ // There are two different kinds of completion we want to provide:
+ // 1. If this is a function call, we want to pop up a tooltip that shows the user
+ // the possible overloads with their argument types and names.
+ // 2. If this is a function definition, we want to offer autocompletion of
+ // the function signature.
+
+ // check if function signature autocompletion is appropriate
+ // Also check if the function name is a destructor name.
+ bool isDestructor = false;
+ if (!functions.isEmpty() && !toolTipOnly) {
+
+ // function definitions will only happen in class or namespace scope,
+ // so get the current location's enclosing scope.
+
+ // get current line and column
+ int lineSigned = 0, columnSigned = 0;
+ Utils::Text::convertPosition(m_interface->textDocument(), m_interface->position(),
+ &lineSigned, &columnSigned);
+ unsigned line = lineSigned, column = columnSigned - 1;
+
+ // find a scope that encloses the current location, starting from the lastVisibileSymbol
+ // and moving outwards
+
+ Scope *sc = context.thisDocument()->scopeAt(line, column);
+
+ if (sc && (sc->isClass() || sc->isNamespace())) {
+ // It may still be a function call. If the whole line parses as a function
+ // declaration, we should be certain that it isn't.
+ bool autocompleteSignature = false;
+
+ QTextCursor tc(m_interface->textDocument());
+ tc.setPosition(endOfExpression);
+ BackwardsScanner bs(tc, m_interface->languageFeatures());
+ const int startToken = bs.startToken();
+ int lineStartToken = bs.startOfLine(startToken);
+ // make sure the required tokens are actually available
+ bs.LA(startToken - lineStartToken);
+ QString possibleDecl = bs.mid(lineStartToken).trimmed().append(QLatin1String("();"));
+
+ Document::Ptr doc = Document::create(QLatin1String("<completion>"));
+ doc->setUtf8Source(possibleDecl.toUtf8());
+ if (doc->parse(Document::ParseDeclaration)) {
+ doc->check();
+ if (SimpleDeclarationAST *sd = doc->translationUnit()->ast()->asSimpleDeclaration()) {
+ if (sd->declarator_list && sd->declarator_list->value->postfix_declarator_list
+ && sd->declarator_list->value->postfix_declarator_list->value->asFunctionDeclarator()) {
+
+ autocompleteSignature = true;
+
+ CoreDeclaratorAST *coreDecl = sd->declarator_list->value->core_declarator;
+ if (coreDecl && coreDecl->asDeclaratorId() && coreDecl->asDeclaratorId()->name) {
+ NameAST *declName = coreDecl->asDeclaratorId()->name;
+ if (declName->asDestructorName()) {
+ isDestructor = true;
+ } else if (QualifiedNameAST *qName = declName->asQualifiedName()) {
+ if (qName->unqualified_name && qName->unqualified_name->asDestructorName())
+ isDestructor = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (autocompleteSignature && !isDestructor) {
+ // set up for rewriting function types with minimally qualified names
+ // to do it correctly we'd need the declaration's context and scope, but
+ // that'd be too expensive to get here. instead, we just minimize locally
+ SubstitutionEnvironment env;
+ env.setContext(context);
+ env.switchScope(sc);
+ ClassOrNamespace *targetCoN = context.lookupType(sc);
+ if (!targetCoN)
+ targetCoN = context.globalNamespace();
+ UseMinimalNames q(targetCoN);
+ env.enter(&q);
+ Control *control = context.bindings()->control().data();
+
+ // set up signature autocompletion
+ foreach (Function *f, functions) {
+ Overview overview;
+ overview.showArgumentNames = true;
+ overview.showDefaultArguments = false;
+
+ const FullySpecifiedType localTy = rewriteType(f->type(), &env, control);
+
+ // gets: "parameter list) cv-spec",
+ const QString completion = overview.prettyType(localTy).mid(1);
+ if (completion == QLatin1String(")"))
+ continue;
+
+ addCompletionItem(completion, QIcon(), 0,
+ QVariant::fromValue(CompleteFunctionDeclaration(f)));
+ }
+ return true;
+ }
+ }
+ }
+
+ if (!functions.empty() && !isDestructor) {
+ m_hintProposal = createHintProposal(functions);
+ return true;
+ }
+
+ return false;
+}
+
+void CppCompletionAssistInterface::getCppSpecifics() const
+{
+ if (m_gotCppSpecifics)
+ return;
+ m_gotCppSpecifics = true;
+
+ if (m_parser) {
+ m_parser->update({CppModelManager::instance()->workingCopy(),
+ nullptr,
+ Language::Cxx,
+ false});
+ m_snapshot = m_parser->snapshot();
+ m_headerPaths = m_parser->headerPaths();
+ }
+}
+
+} // CppEditor::Internal