Files
qt-creator/src/plugins/cpptools/cppcompletionassist.cpp

2131 lines
76 KiB
C++
Raw Normal View History

/****************************************************************************
2008-12-02 12:01:29 +01:00
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
2008-12-02 12:01:29 +01:00
**
** This file is part of Qt Creator.
2008-12-02 12:01:29 +01:00
**
** 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.
2010-12-17 16:01:08 +01:00
**
****************************************************************************/
2008-12-02 15:08:31 +01:00
#include "cppcompletionassist.h"
#include "builtineditordocumentparser.h"
#include "cppdoxygen.h"
#include "cppmodelmanager.h"
#include "cpptoolsconstants.h"
#include "cpptoolsreuse.h"
C++: Base parsing on editor document instead of widget This mainly takes CppEditorSupport apart. * Parsing is now invoked by CPPEditorDocument itself by listening to QTextDocument::contentsChanged(). * Upon construction and destruction CPPEditorDocument creates and deletes an EditorDocumentHandle for (un)registration in the model manager. This handle provides everything to generate the working copy and to access the editor document processor. * A CPPEditorDocument owns a BaseEditorDocumentProcessor instance that controls parsing, semantic info recalculation and the semantic highlighting for the document. This is more or less what is left from CppEditorSupport and can be considered as the backend of a CPPEditorDocument. CPPEditorDocument itself is quite small. * BuiltinEditorDocumentProcessor and ClangEditorDocumentProcessor derive from BaseEditorDocumentProcessor and implement the gaps. * Since the semantic info calculation was bound to the widget, it also calculated the local uses, which depend on the cursor position. This calculation got moved into the extracted class UseSeletionsUpdater in the cppeditor plugin, which is run once the cursor position changes or the semantic info document is updated. * Some more logic got extracted: - SemanticInfoUpdater (logic was in CppEditorSupport) - SemanticHighlighter (logic was in CppEditorSupport) * The *Parser and *Processor classes can be easily accessed by the static function get(). * CppHighlightingSupport is gone since it turned out to be useless. * The editor dependency in CompletionAssistProviders is gone since we actually only need the file path now. Change-Id: I49d3a7bd138c5ed9620123e34480772535156508 Reviewed-by: Orgad Shaneh <orgads@gmail.com> Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
2014-08-19 15:59:29 +02:00
#include "editordocumenthandle.h"
2008-12-02 12:01:29 +01:00
#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/convenience.h>
#include <texteditor/snippets/snippet.h>
#include <texteditor/texteditorsettings.h>
#include <texteditor/completionsettings.h>
#include <utils/mimetypes/mimedatabase.h>
#include <utils/qtcassert.h>
2008-12-02 12:01:29 +01:00
#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>
2010-05-12 14:52:24 +02:00
2008-12-02 12:01:29 +01:00
using namespace CPlusPlus;
using namespace CppEditor;
using namespace CppTools;
using namespace CppTools::Internal;
using namespace TextEditor;
2008-12-02 12:01:29 +01:00
namespace CppTools {
namespace Internal {
struct CompleteFunctionDeclaration
{
explicit CompleteFunctionDeclaration(Function *f = 0)
: function(f)
{}
Function *function;
};
// ---------------------
// CppAssistProposalItem
// ---------------------
class CppAssistProposalItem final : public AssistProposalItem
{
public:
CppAssistProposalItem() :
m_isOverloaded(false) {}
~CppAssistProposalItem() Q_DECL_NOEXCEPT {}
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; }
quint64 hash() const override;
private:
QSharedPointer<TypeOfExpression> m_typeOfExpression;
unsigned m_completionOperator;
mutable QChar m_typedChar;
bool m_isOverloaded;
};
} // Internal
} // CppTools
Q_DECLARE_METATYPE(CppTools::Internal::CompleteFunctionDeclaration)
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()) {
CppAssistProposalItem *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 = 0;
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;
}
// Determine the length of characters that should just be kept on the editor, but do
// not consider content that ends as an identifier (which could be undesired).
const int lineEnd = manipulator.positionAt(EndOfLinePosition);
const QString inEditor = manipulator.textAt(manipulator.currentPosition(),
lineEnd - manipulator.currentPosition());
int preserveLength = 0;
if (!inEditor.isEmpty()) {
preserveLength = toInsert.length() - (manipulator.currentPosition() - basePosition);
const int inEditorLength = inEditor.length();
while (preserveLength > 0) {
if (inEditor.startsWith(toInsert.right(preserveLength))
&& (inEditorLength == preserveLength
|| !CppTools::isValidIdentifierChar(inEditor.at(preserveLength)))) {
break;
}
--preserveLength;
}
}
for (int i = 0; i < extraChars.length(); ++i) {
const QChar a = extraChars.at(i);
const QChar b = manipulator.characterAt(manipulator.currentPosition() + i + preserveLength);
if (a == b)
++extraLength;
else
break;
}
toInsert += extraChars;
// Insert the remainder of the name
const int length = manipulator.currentPosition() - basePosition + preserveLength + extraLength;
manipulator.replace(basePosition, length, toInsert);
if (cursorOffset)
manipulator.setCursorPosition(manipulator.currentPosition() + cursorOffset);
if (setAutoCompleteSkipPos)
manipulator.setAutoCompleteSkipPosition(manipulator.currentPosition());
}
// --------------------
// CppFunctionHintModel
// --------------------
class CppFunctionHintModel : public IFunctionHintProposalModel
{
public:
CppFunctionHintModel(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 QString &filePath,
const TextEditorWidget *textEditorWidget,
const LanguageFeatures &languageFeatures,
int position,
AssistReason reason) const
{
QTC_ASSERT(textEditorWidget, return 0);
CppTools: Fix data race when accessing the editor revision Addresses the following findings of QTCREATORBUG-12030: * qtc.helgrind.plugintests.txt#2 * qtc.helgrind.usingEditors1.txt#1 Helgrind report (truncated): Possible data race during write of size 4 at 0x23679618 by thread #1 Locks held: none at 0x6819003: ??? by 0x681D713: ??? by 0x68200DE: ??? by 0x684B8F8: QTextCursor::insertText(QString const&, QTextCharFormat const&) by 0x684BCB9: QTextCursor::insertText(QString const&) by 0x139DA06C: TextEditor::BaseTextEditorWidget::keyPressEvent(QKeyEvent*) (basetexteditor.cpp:1866) by 0x184C999F: CppEditor::Internal::CPPEditorWidget::keyPressEvent(QKeyEvent*) (cppeditor.cpp:1416) This conflicts with a previous read of size 4 by thread #18 Locks held: none at 0x680BC54: QTextDocument::revision() const by 0x159047F3: CppTools::CppEditorSupport::editorRevision() const (cpptoolseditorsupport.cpp:198) by 0x158E39BF: CppTools::Internal::CppModelManager::buildWorkingCopyList() (cppmodelmanager.cpp:525) by 0x158E3D5B: CppTools::Internal::CppModelManager::workingCopy() const (cppmodelmanager.cpp:544) by 0x1589FF6F: CppTools::Internal::CppCompletionAssistInterface::getCppSpecifics() const (cppcompletionassist.cpp:1957) by 0x158A1158: CppTools::Internal::CppCompletionAssistInterface::snapshot() const (cppcompletionassist.h:200) by 0x1589707C: CppTools::Internal::CppCompletionAssistProcessor::startCompletionInternal(QString, unsigned int, unsigned int, QString const&, int) (cppcompletionassist.cpp:1212) by 0x15893CC7: CppTools::Internal::CppCompletionAssistProcessor::startCompletionHelper() (cppcompletionassist.cpp:970) --- Possible data race during write of size 4 at 0x24C8AD18 by thread #1 Locks held: none at 0x684AF23: QTextCursor::beginEditBlock() by 0x139D7D05: TextEditor::BaseTextEditorWidget::keyPressEvent(QKeyEvent*) (basetexteditor.cpp:1578) by 0x184C999F: CppEditor::Internal::CPPEditorWidget::keyPressEvent(QKeyEvent*) (cppeditor.cpp:1416) ... by 0x40F15A: main (main.cpp:533) This conflicts with a previous read of size 4 by thread #11 Locks held: none at 0x680BC54: QTextDocument::revision() const by 0x159048D3: CppTools::CppEditorSupport::editorRevision() const (cpptoolseditorsupport.cpp:198) by 0x158E3A9F: CppTools::Internal::CppModelManager::buildWorkingCopyList() (cppmodelmanager.cpp:525) by 0x158E3E3B: CppTools::Internal::CppModelManager::workingCopy() const (cppmodelmanager.cpp:544) by 0x1590741E: parse(QFutureInterface<void>&, QSharedPointer<CppTools::SnapshotUpdater>) (cpptoolseditorsupport.cpp:299) Task-number: QTCREATORBUG-12030 Change-Id: Idf0aa47f1f6bfd6814a961fe39d3b19b98f934f5 Reviewed-by: Orgad Shaneh <orgads@gmail.com> Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
2014-04-10 11:13:37 -04:00
return new CppCompletionAssistInterface(filePath,
textEditorWidget,
BuiltinEditorDocumentParser::get(filePath),
languageFeatures,
position,
reason,
CppModelManager::instance()->workingCopy());
}
// -----------------
// CppAssistProposal
// -----------------
class CppAssistProposal : public GenericProposal
{
public:
CppAssistProposal(int cursorPos, GenericProposalModel *model)
: GenericProposal(cursorPos, model)
, m_replaceDotForArrow(static_cast<CppAssistProposalModel *>(model)->m_replaceDotForArrow)
{}
bool isCorrective() 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;
// The current symbol.
Symbol *_symbol;
// The pretty printer.
Overview overview;
public:
ConvertToCompletionItem()
: _item(0)
, _symbol(0)
{
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 0;
AssistProposalItem *previousItem = switchCompletionItem(0);
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)
{
_item = newCompletionItem(name);
if (!_symbol->isScope() || _symbol->isFunction())
_item->setDetail(overview.prettyType(_symbol->type(), name));
}
void visit(const TemplateNameId *name)
{
_item = newCompletionItem(name);
_item->setText(QString::fromUtf8(name->identifier()->chars(), name->identifier()->size()));
}
void visit(const DestructorNameId *name)
{ _item = newCompletionItem(name); }
void visit(const OperatorNameId *name)
{
_item = newCompletionItem(name);
_item->setDetail(overview.prettyType(_symbol->type(), name));
}
void visit(const ConversionNameId *name)
{ _item = newCompletionItem(name); }
void visit(const QualifiedNameId *name)
{ _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 0;
}
Scope *enclosingNonTemplateScope(Symbol *symbol)
{
if (symbol) {
if (Scope *scope = symbol->enclosingScope()) {
if (Template *templ = scope->asTemplate())
return templ->enclosingScope();
return scope;
}
}
return 0;
}
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 0;
}
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 = 0;
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 0; // not a pointer or a reference to a pointer.
NamedType *namedType = type->asNamedType();
if (!namedType) // not a class name.
return 0;
name = namedType->name();
}
return name ? context.lookupType(name, lookupItem.scope()) : 0;
}
Class *classFromLookupItem(const LookupItem &lookupItem, const LookupContext &context)
{
ClassOrNamespace *b = classOrNamespaceFromLookupItem(lookupItem, context);
if (!b)
return 0;
foreach (Symbol *s, b->symbols()) {
if (Class *klass = s->asClass())
return klass;
}
return 0;
}
const Name *minimalName(Symbol *symbol, Scope *targetScope, const LookupContext &context)
{
ClassOrNamespace *target = context.lookupType(targetScope);
if (!target)
target = context.globalNamespace();
return context.minimalName(symbol, target, context.bindings()->control().data());
}
} // Anonymous
// ------------------------------------
// InternalCppCompletionAssistProcessor
// ------------------------------------
InternalCppCompletionAssistProcessor::InternalCppCompletionAssistProcessor()
: m_model(new CppAssistProposalModel)
{
}
InternalCppCompletionAssistProcessor::~InternalCppCompletionAssistProcessor()
{}
IAssistProposal * InternalCppCompletionAssistProcessor::perform(const AssistInterface *interface)
{
m_interface.reset(static_cast<const CppCompletionAssistInterface *>(interface));
if (interface->reason() != ExplicitlyInvoked && !accepts())
return 0;
int index = startCompletionHelper();
if (index != -1) {
if (m_hintProposal)
return m_hintProposal;
return createContentProposal();
}
return 0;
}
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 three 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);
2010-07-19 14:06:00 +02:00
if (pos - startOfName >= 3) {
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);
const QStringRef &identifier =
line.midRef(idToken.utf16charsBegin(),
idToken.utf16charsEnd() - idToken.utf16charsBegin());
if (identifier == QLatin1String("include")
|| identifier == QLatin1String("include_next")
|| (m_interface->languageFeatures().objCEnabled && identifier == QLatin1String("import"))) {
return true;
}
}
}
}
}
}
2008-12-02 12:01:29 +01:00
return false;
}
IAssistProposal *InternalCppCompletionAssistProcessor::createContentProposal()
2008-12-02 12:01:29 +01:00
{
// Duplicates are kept only if they are snippets.
QSet<QString> processed;
auto it = m_completions.begin();
while (it != m_completions.end()) {
CppAssistProposalItem *item = static_cast<CppAssistProposalItem *>(*it);
if (!processed.contains(item->text()) || item->isSnippet()) {
++it;
if (!item->isSnippet()) {
processed.insert(item->text());
if (!item->isOverloaded()) {
if (Symbol *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.take());
}
IAssistProposal *InternalCppCompletionAssistProcessor::createHintProposal(
QList<Function *> functionSymbols) const
{
IFunctionHintProposalModel *model =
new CppFunctionHintModel(functionSymbols, m_model->m_typeOfExpression);
IAssistProposal *proposal = new FunctionHintProposal(m_positionForProposal, model);
return proposal;
}
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 (CppTools::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;
Convenience::convertPosition(m_interface->textDocument(), startOfExpression, &line, &column);
const QString fileName = m_interface->fileName();
return startCompletionInternal(fileName, line, column, 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->fileName());
if (!thisDocument)
return false;
m_model->m_typeOfExpression->init(thisDocument, m_interface->snapshot());
int line = 0, column = 0;
Convenience::convertPosition(m_interface->textDocument(), m_interface->position(), &line, &column);
Scope *scope = thisDocument->scopeAt(line, column);
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);
}
2008-12-02 12:01:29 +01:00
foreach (Scope *scope, memberScopes) {
for (unsigned i = 0; i < scope->memberCount(); ++i) {
Symbol *symbol = scope->memberAt(i);
2008-12-02 12:01:29 +01:00
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 (unsigned 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;
2008-12-02 12:01:29 +01:00
if (!text.isEmpty())
addCompletionItem(text, QIcon(), 0, QVariant::fromValue(data));
}
}
2009-02-20 12:55:01 +01:00
}
}
}
2009-02-20 12:55:01 +01:00
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
ProjectPartHeaderPaths headerPaths = m_interface->headerPaths();
const ProjectPartHeaderPath currentFilePath(QFileInfo(m_interface->fileName()).path(),
ProjectPartHeaderPath::IncludePath);
if (!headerPaths.contains(currentFilePath))
headerPaths.append(currentFilePath);
Utils::MimeDatabase mdb;
const QStringList suffixes = mdb.mimeTypeForName(QLatin1String("text/x-c++hdr")).suffixes();
foreach (const ProjectPartHeaderPath &headerPath, headerPaths) {
QString realPath = headerPath.path;
if (!directoryPrefix.isEmpty()) {
realPath += QLatin1Char('/');
realPath += directoryPrefix;
if (headerPath.isFrameworkPath())
realPath += QLatin1String(".framework/Headers");
}
completeInclude(realPath, suffixes);
}
return !m_completions.isEmpty();
}
2009-02-20 12:55:01 +01:00
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);
2009-02-20 12:55:01 +01:00
if (objcKeywordsWanted())
addCompletionItem(QLatin1String("import"));
}
2009-02-20 12:55:01 +01:00
bool InternalCppCompletionAssistProcessor::objcKeywordsWanted() const
{
if (!m_interface->languageFeatures().objCEnabled)
return false;
2009-02-20 12:55:01 +01:00
const QString fileName = m_interface->fileName();
2008-12-02 12:01:29 +01:00
Utils::MimeDatabase mdb;
const Utils::MimeType mt = mdb.mimeTypeForFile(fileName);
return mt.matchesName(QLatin1String(CppTools::Constants::OBJECTIVE_C_SOURCE_MIMETYPE))
|| mt.matchesName(QLatin1String(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE));
}
2008-12-02 12:01:29 +01:00
int InternalCppCompletionAssistProcessor::startCompletionInternal(const QString &fileName,
unsigned line, unsigned column,
const QString &expr,
int endOfExpression)
{
QString expression = expr.trimmed();
Document::Ptr thisDocument = m_interface->snapshot().document(fileName);
if (!thisDocument)
return -1;
2008-12-02 12:01:29 +01:00
m_model->m_typeOfExpression->init(thisDocument, m_interface->snapshot());
Scope *scope = thisDocument->scopeAt(line, column);
QTC_ASSERT(scope != 0, return -1);
2008-12-02 12:01:29 +01:00
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;
}
2008-12-02 12:01:29 +01:00
if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) {
2008-12-02 12:01:29 +01:00
// Apply signal/slot completion on 'this'
expression = QLatin1String("this");
}
}
2008-12-02 12:01:29 +01:00
QByteArray utf8Exp = expression.toUtf8();
QList<LookupItem> results =
(*m_model->m_typeOfExpression)(utf8Exp, scope, TypeOfExpression::Preprocess);
2010-05-12 14:52:24 +02:00
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);
}
2009-02-17 12:12:14 +01:00
if (results.isEmpty())
return -1;
2009-02-17 12:12:14 +01:00
} 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 = 0;
for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) {
if (Block *block = scope->asBlock()) {
if (ClassOrNamespace *binding = context.lookupType(scope)) {
for (unsigned 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 (unsigned i = 0; i < scope->memberCount(); ++i)
addCompletionItem(scope->memberAt(i), FunctionLocalsOrder);
} else if (Function *fun = scope->asFunction()) {
for (unsigned i = 0, argc = fun->argumentCount(); i < argc; ++i)
addCompletionItem(fun->argumentAt(i), FunctionArgumentsOrder);
} else if (Template *templ = scope->asTemplate()) {
for (unsigned 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();
2008-12-02 12:01:29 +01:00
}
bool InternalCppCompletionAssistProcessor::completeMember(const QList<LookupItem> &baseResults)
2008-12-02 12:01:29 +01:00
{
const LookupContext &context = m_model->m_typeOfExpression->context();
if (baseResults.isEmpty())
2008-12-29 11:53:40 +01:00
return false;
2008-12-02 12:01:29 +01:00
ResolveExpression resolveExpression(context);
bool *replaceDotForArrow = 0;
if (!m_interface->languageFeatures().objCEnabled)
replaceDotForArrow = &m_model->m_replaceDotForArrow;
if (ClassOrNamespace *binding =
resolveExpression.baseExpression(baseResults,
m_model->m_completionOperator,
replaceDotForArrow)) {
2010-05-12 14:52:24 +02:00
if (binding)
completeClass(binding, /*static lookup = */ true);
2008-12-02 12:01:29 +01:00
return !m_completions.isEmpty();
}
2008-12-02 12:01:29 +01:00
return false;
}
bool InternalCppCompletionAssistProcessor::completeScope(const QList<LookupItem> &results)
2008-12-02 12:01:29 +01:00
{
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;
}
2008-12-02 12:01:29 +01:00
} else if (Class *classTy = ty->asClassType()) {
if (ClassOrNamespace *b = context.lookupType(classTy)) {
completeClass(b);
break;
}
2008-12-02 12:01:29 +01:00
// 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();
2008-12-02 12:01:29 +01:00
}
void InternalCppCompletionAssistProcessor::completeNamespace(ClassOrNamespace *b)
2008-12-02 12:01:29 +01:00
{
QSet<ClassOrNamespace *> bindingsVisited;
QList<ClassOrNamespace *> bindingsToVisit;
bindingsToVisit.append(b);
2008-12-02 12:01:29 +01:00
while (!bindingsToVisit.isEmpty()) {
ClassOrNamespace *binding = bindingsToVisit.takeFirst();
if (!binding || bindingsVisited.contains(binding))
continue;
bindingsVisited.insert(binding);
bindingsToVisit += binding->usings();
2008-12-02 12:01:29 +01:00
QList<Scope *> scopesToVisit;
QSet<Scope *> scopesVisited;
foreach (Symbol *bb, binding->symbols()) {
if (Scope *scope = bb->asScope())
scopesToVisit.append(scope);
}
foreach (Enum *e, binding->unscopedEnums())
2010-08-11 12:26:02 +02:00
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);
}
2008-12-02 12:01:29 +01:00
}
}
}
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();
2008-12-02 12:01:29 +01:00
QList<Scope *> scopesToVisit;
QSet<Scope *> scopesVisited;
2008-12-02 12:01:29 +01:00
foreach (Symbol *bb, binding->symbols()) {
if (Class *k = bb->asClass())
2010-08-11 12:26:02 +02:00
scopesToVisit.append(k);
else if (Block *b = bb->asBlock())
scopesToVisit.append(b);
}
foreach (Enum *e, binding->unscopedEnums())
2010-08-11 12:26:02 +02:00
scopesToVisit.append(e);
while (!scopesToVisit.isEmpty()) {
Scope *scope = scopesToVisit.takeFirst();
if (!scope || scopesVisited.contains(scope))
2008-12-02 12:01:29 +01:00
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);
2008-12-02 12:01:29 +01:00
}
if (member->isPublic())
addCompletionItem(member, PublicClassMemberOrder);
else
addCompletionItem(member);
2008-12-02 12:01:29 +01:00
}
std::set<Class *>::const_iterator citEnd = nestedAnonymouses.end();
for (std::set<Class *>::const_iterator cit = nestedAnonymouses.begin(); cit != citEnd; ++cit)
addClassMembersToCompletion(*cit, staticLookup);
2008-12-02 12:01:29 +01:00
}
bool InternalCppCompletionAssistProcessor::completeQtMethod(const QList<LookupItem> &results,
CompleteQtMethodMode type)
2008-12-02 12:01:29 +01:00
{
if (results.isEmpty())
return false;
const LookupContext &context = m_model->m_typeOfExpression->context();
ConvertToCompletionItem toCompletionItem;
2008-12-02 12:01:29 +01:00
Overview o;
o.showReturnTypes = false;
o.showArgumentNames = false;
o.showFunctionSignatures = true;
2008-12-02 12:01:29 +01:00
QSet<QString> signatures;
foreach (const LookupItem &lookupItem, results) {
ClassOrNamespace *b = classOrNamespaceFromLookupItem(lookupItem, context);
if (!b)
2010-05-05 12:06:38 +02:00
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);
2008-12-02 12:01:29 +01:00
foreach (Symbol *s, binding->symbols())
if (Class *clazz = s->asClass())
2010-08-11 12:26:02 +02:00
scopes.append(clazz);
2008-12-02 12:01:29 +01:00
todo.append(binding->usings());
}
2010-05-06 10:36:11 +02:00
}
const bool wantSignals = type == CompleteQt4Signals || type == CompleteQt5Signals;
const bool wantQt5SignalOrSlot = type == CompleteQt5Signals || type == CompleteQt5Slots;
foreach (Scope *scope, scopes) {
Class *klass = scope->asClass();
if (!klass)
2008-12-02 12:01:29 +01:00
continue;
2010-08-11 12:26:02 +02:00
for (unsigned i = 0; i < scope->memberCount(); ++i) {
Symbol *member = scope->memberAt(i);
Function *fun = member->type()->asFunctionType();
if (!fun || fun->isGenerated())
2008-12-02 12:01:29 +01:00
continue;
if (wantSignals && !fun->isSignal())
2008-12-02 12:01:29 +01:00
continue;
else if (!wantSignals && type == CompleteQt4Slots && !fun->isSlot())
2008-12-02 12:01:29 +01:00
continue;
unsigned count = fun->argumentCount();
while (true) {
const QString completionText = wantQt5SignalOrSlot
? createQt5SignalOrSlot(fun, o)
: createQt4SignalOrSlot(fun, o);
2008-12-02 12:01:29 +01:00
if (!signatures.contains(completionText)) {
AssistProposalItem *ci = toCompletionItem(fun);
if (!ci)
2008-12-02 12:01:29 +01:00
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);
2008-12-02 12:01:29 +01:00
}
if (count && fun->argumentAt(count - 1)->asArgument()->hasInitializer())
--count;
else
break;
2008-12-02 12:01:29 +01:00
}
}
}
}
return !m_completions.isEmpty();
2008-12-02 12:01:29 +01:00
}
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 = Icons::iconForType(Icons::ClassIconType);
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;
2010-07-19 14:06:00 +02:00
// keyword completion items.
for (int i = T_FIRST_KEYWORD; i < keywordLimit; ++i)
addCompletionItem(QLatin1String(Token::name(i)), Icons::keywordIcon(), KeywordsOrder);
// primitive type completion items.
for (int i = T_FIRST_PRIMITIVE; i <= T_LAST_PRIMITIVE; ++i)
addCompletionItem(QLatin1String(Token::name(i)), Icons::keywordIcon(), KeywordsOrder);
// "Identifiers with special meaning"
if (m_interface->languageFeatures().cxx11Enabled) {
addCompletionItem(QLatin1String("override"), Icons::keywordIcon(), KeywordsOrder);
addCompletionItem(QLatin1String("final"), Icons::keywordIcon(), KeywordsOrder);
}
2008-12-02 12:01:29 +01:00
}
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 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)
2008-12-02 12:01:29 +01:00
{
const LookupContext &context = m_model->m_typeOfExpression->context();
QList<Function *> functions;
foreach (const LookupItem &result, results) {
FullySpecifiedType exprTy = result.type().simplified();
2008-12-02 12:01:29 +01:00
if (Class *klass = asClassOrTemplateClassType(exprTy)) {
const Name *className = klass->name();
if (!className)
continue; // nothing to do for anonymous classes.
2008-12-02 12:01:29 +01:00
for (unsigned 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;
}
}
2008-12-02 12:01:29 +01:00
if (functions.isEmpty()) {
foreach (const LookupItem &result, results) {
FullySpecifiedType ty = result.type().simplified();
2008-12-02 12:01:29 +01:00
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.
}
2008-12-02 12:01:29 +01:00
bool newOverload = true;
foreach (Function *f, functions) {
if (fun->match(f)) {
newOverload = false;
break;
2008-12-02 12:01:29 +01:00
}
}
if (newOverload)
functions.append(fun);
}
}
}
2008-12-02 12:01:29 +01:00
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);
2008-12-02 12:01:29 +01:00
}
}
}
}
}
2008-12-02 12:01:29 +01:00
// 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;
Convenience::convertPosition(m_interface->textDocument(), m_interface->position(), &lineSigned, &columnSigned);
unsigned line = lineSigned, column = columnSigned;
// find a scope that encloses the current location, starting from the lastVisibileSymbol
// and moving outwards
2008-12-02 12:01:29 +01:00
Scope *sc = context.thisDocument()->scopeAt(line, column);
2008-12-02 12:01:29 +01:00
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;
2008-12-02 12:01:29 +01:00
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()) {
2008-12-02 12:01:29 +01:00
autocompleteSignature = true;
2008-12-02 12:01:29 +01:00
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;
}
}
}
}
}
2008-12-02 12:01:29 +01:00
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;
2010-09-24 20:16:34 +02:00
}
void CppCompletionAssistInterface::getCppSpecifics() const
{
if (m_gotCppSpecifics)
return;
m_gotCppSpecifics = true;
if (m_parser) {
m_parser->update(CppTools::CppModelManager::instance()->workingCopy());
m_snapshot = m_parser->snapshot();
m_headerPaths = m_parser->headerPaths();
}
}