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

1965 lines
71 KiB
C++
Raw Normal View History

/****************************************************************************
2008-12-02 12:01:29 +01:00
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
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 Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
2010-12-17 16:01:08 +01:00
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
2008-12-02 15:08:31 +01:00
#include "cppcompletionassist.h"
#include "builtineditordocumentparser.h"
#include "cppdoxygen.h"
#include "cppmodelmanager.h"
#include "cppmodelmanagerinterface.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/basicproposalitem.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/qtcassert.h>
#include <utils/qtcoverride.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 <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 : public TextEditor::BasicProposalItem
{
public:
CppAssistProposalItem() :
m_isOverloaded(false) {}
bool prematurelyApplies(const QChar &c) const QTC_OVERRIDE;
void applyContextualContent(TextEditor::BaseTextEditor *editor,
int basePosition) const QTC_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; }
private:
bool m_isOverloaded;
mutable QChar m_typedChar;
unsigned m_completionOperator;
QSharedPointer<TypeOfExpression> m_typeOfExpression;
};
} // 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();
}
IAssistProposalItem *CppAssistProposalModel::proposalItem(int index) const
{
BasicProposalItem *item =
static_cast<BasicProposalItem *>(BasicProposalItemListModel::proposalItem(index));
if (!item->data().canConvert<QString>()) {
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(TextEditor::BaseTextEditor *editor, int basePosition)
{
QTextCursor cursor = editor->editorWidget()->textCursor();
cursor.setPosition(basePosition);
BackwardsScanner scanner(cursor);
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;
}
void CppAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor *editor,
int basePosition) const
{
Symbol *symbol = 0;
if (data().isValid())
symbol = data().value<Symbol *>();
QString toInsert;
QString extraChars;
int extraLength = 0;
int cursorOffset = 0;
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(editor, 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 = editor->textDocument()->characterAt(editor->position());
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 = editor->textDocument()->characterAt(editor->position() + 1);
if (MatchingText::shouldInsertMatchingText(lookAhead)) {
extraChars += QLatin1Char(')');
--cursorOffset;
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 = editor->position(TextEditor::BaseTextEditor::EndOfLine);
const QString inEditor = editor->textDocument()->textAt(editor->position(),
lineEnd - editor->position());
int preserveLength = 0;
if (!inEditor.isEmpty()) {
preserveLength = toInsert.length() - (editor->position() - 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 = editor->textDocument()->characterAt(editor->position() + i + preserveLength);
if (a == b)
++extraLength;
else
break;
}
toInsert += extraChars;
// Insert the remainder of the name
const int length = editor->position() - basePosition + preserveLength + extraLength;
editor->setCursorPosition(basePosition);
editor->replace(length, toInsert);
if (cursorOffset)
editor->setCursorPosition(editor->position() + cursorOffset);
}
// --------------------
// CppFunctionHintModel
// --------------------
class CppFunctionHintModel : public TextEditor::IFunctionHintProposalModel
{
public:
CppFunctionHintModel(QList<Function *> functionSymbols,
const QSharedPointer<TypeOfExpression> &typeOfExp)
: m_functionSymbols(functionSymbols)
, m_currentArg(-1)
, m_typeOfExpression(typeOfExp)
{}
void reset() QTC_OVERRIDE {}
int size() const QTC_OVERRIDE { return m_functionSymbols.size(); }
QString text(int index) const QTC_OVERRIDE;
int activeArgument(const QString &prefix) const QTC_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 += Qt::escape(prettyMethod.left(begin));
hintText += QLatin1String("<b>");
hintText += Qt::escape(prettyMethod.mid(begin, end - begin));
hintText += QLatin1String("</b>");
hintText += Qt::escape(prettyMethod.mid(end));
return hintText;
}
int CppFunctionHintModel::activeArgument(const QString &prefix) const
{
int argnr = 0;
int parcount = 0;
SimpleLexer tokenize;
QList<Token> 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 CppCompletionAssistProcessor;
}
TextEditor::IAssistInterface *InternalCompletionAssistProvider::createAssistInterface(
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
ProjectExplorer::Project *project, const QString &filePath, QTextDocument *document,
bool isObjCEnabled, int position, TextEditor::AssistReason reason) const
{
Q_UNUSED(project);
QTC_ASSERT(document, 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
CppModelManagerInterface *modelManager = CppModelManagerInterface::instance();
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
return new CppTools::Internal::CppCompletionAssistInterface(filePath, document, isObjCEnabled,
position, reason,
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
modelManager->workingCopy());
}
// -----------------
// CppAssistProposal
// -----------------
class CppAssistProposal : public TextEditor::GenericProposal
{
public:
CppAssistProposal(int cursorPos, TextEditor::IGenericProposalModel *model)
: TextEditor::GenericProposal(cursorPos, model)
, m_replaceDotForArrow(static_cast<CppAssistProposalModel *>(model)->m_replaceDotForArrow)
{}
bool isCorrective() const QTC_OVERRIDE { return m_replaceDotForArrow; }
void makeCorrection(BaseTextEditor *editor) QTC_OVERRIDE;
private:
bool m_replaceDotForArrow;
};
void CppAssistProposal::makeCorrection(BaseTextEditor *editor)
{
const int oldPosition = editor->position();
editor->setCursorPosition(basePosition() - 1);
editor->replace(1, QLatin1String("->"));
editor->setCursorPosition(oldPosition + 1);
moveBasePosition(1);
}
namespace {
class ConvertToCompletionItem: protected NameVisitor
{
// The completion item.
BasicProposalItem *_item;
// The current symbol.
Symbol *_symbol;
// The pretty printer.
Overview overview;
public:
ConvertToCompletionItem()
: _item(0)
, _symbol(0)
{
overview.showReturnTypes = true;
overview.showArgumentNames = true;
}
BasicProposalItem *operator()(Symbol *symbol)
{
//using declaration can be qualified
if (!symbol || !symbol->name() || (symbol->name()->isQualifiedNameId()
&& !symbol->asUsingDeclaration()))
return 0;
BasicProposalItem *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;
}
BasicProposalItem *switchCompletionItem(BasicProposalItem *item)
{
BasicProposalItem *previousItem = _item;
_item = item;
return previousItem;
}
BasicProposalItem *newCompletionItem(const Name *name)
{
BasicProposalItem *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;
}
} // Anonymous
// ----------------------------
// CppCompletionAssistProcessor
// ----------------------------
CppCompletionAssistProcessor::CppCompletionAssistProcessor()
: m_startPosition(-1)
, m_snippetCollector(QLatin1String(CppEditor::Constants::CPP_SNIPPETS_GROUP_ID),
QIcon(QLatin1String(":/texteditor/images/snippet.png")))
, preprocessorCompletions(QStringList()
<< QLatin1String("define")
<< QLatin1String("error")
<< QLatin1String("include")
<< QLatin1String("line")
<< QLatin1String("pragma")
<< QLatin1String("undef")
<< QLatin1String("if")
<< QLatin1String("ifdef")
<< QLatin1String("ifndef")
<< QLatin1String("elif")
<< QLatin1String("else")
<< QLatin1String("endif"))
, m_model(new CppAssistProposalModel)
, m_hintProposal(0)
{
// FIXME: C++11?
m_languageFeatures.objCEnabled = true;
m_languageFeatures.qtEnabled = true;
m_languageFeatures.qtKeywordsEnabled = true;
m_languageFeatures.qtMocRunEnabled = true;
}
CppCompletionAssistProcessor::~CppCompletionAssistProcessor()
{}
IAssistProposal * CppCompletionAssistProcessor::perform(const IAssistInterface *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 CppCompletionAssistProcessor::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);
LanguageFeatures features;
features.qtEnabled = true;
features.qtMocRunEnabled = true;
features.qtKeywordsEnabled = true;
features.objCEnabled = true;
SimpleLexer tokenize;
tokenize.setLanguageFeatures(features);
tokenize.setSkipComments(false);
const QList<Token> &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_languageFeatures.objCEnabled && identifier == QLatin1String("import"))) {
return true;
}
}
}
}
}
}
2008-12-02 12:01:29 +01:00
return false;
}
IAssistProposal *CppCompletionAssistProcessor::createContentProposal()
2008-12-02 12:01:29 +01:00
{
// Duplicates are kept only if they are snippets.
QSet<QString> processed;
QList<BasicProposalItem *>::iterator it = m_completions.begin();
while (it != m_completions.end()) {
CppAssistProposalItem *item = static_cast<CppAssistProposalItem *>(*it);
if (!processed.contains(item->text()) || item->data().canConvert<QString>()) {
++it;
if (!item->data().canConvert<QString>()) {
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_startPosition, m_model.take());
}
IAssistProposal *CppCompletionAssistProcessor::createHintProposal(
QList<CPlusPlus::Function *> functionSymbols) const
{
IFunctionHintProposalModel *model =
new CppFunctionHintModel(functionSymbols, m_model->m_typeOfExpression);
IAssistProposal *proposal = new FunctionHintProposal(m_startPosition, model);
return proposal;
}
int CppCompletionAssistProcessor::startOfOperator(int pos,
unsigned *kind,
bool wantFunctionCall) const
{
const QChar ch = pos > -1 ? m_interface->characterAt(pos - 1) : QChar();
const QChar ch2 = pos > 0 ? m_interface->characterAt(pos - 2) : QChar();
const QChar ch3 = pos > 1 ? m_interface->characterAt(pos - 3) : QChar();
int start = pos - CppCompletionAssistProvider::activationSequenceChar(ch, ch2, ch3, kind, wantFunctionCall);
if (start != pos) {
QTextCursor tc(m_interface->textDocument());
tc.setPosition(pos);
// Include completion: make sure the quote character is the first one on the line
if (*kind == T_STRING_LITERAL) {
QTextCursor s = tc;
s.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
QString sel = s.selectedText();
if (sel.indexOf(QLatin1Char('"')) < sel.length() - 1) {
*kind = T_EOF_SYMBOL;
start = pos;
}
}
if (*kind == T_COMMA) {
ExpressionUnderCursor expressionUnderCursor;
if (expressionUnderCursor.startOfFunctionCall(tc) == -1) {
*kind = T_EOF_SYMBOL;
start = pos;
}
}
SimpleLexer tokenize;
tokenize.setLanguageFeatures(m_languageFeatures);
tokenize.setSkipComments(false);
const QList<Token> &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor
const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) {
*kind = T_EOF_SYMBOL;
start = pos;
}
// Don't complete in comments or strings, but still check for include completion
else if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT) ||
(tk.isLiteral() && (*kind != T_STRING_LITERAL
&& *kind != T_ANGLE_STRING_LITERAL
&& *kind != T_SLASH
&& *kind != T_DOT))) {
*kind = T_EOF_SYMBOL;
start = pos;
// Include completion: can be triggered by slash, but only in a string
} else if (*kind == T_SLASH && (tk.isNot(T_STRING_LITERAL) && tk.isNot(T_ANGLE_STRING_LITERAL))) {
*kind = T_EOF_SYMBOL;
start = pos;
} else if (*kind == T_LPAREN) {
if (tokenIdx > 0) {
const Token &previousToken = tokens.at(tokenIdx - 1); // look at the token at the left of T_LPAREN
switch (previousToken.kind()) {
case T_IDENTIFIER:
case T_GREATER:
case T_SIGNAL:
case T_SLOT:
break; // good
default:
// that's a bad token :)
*kind = T_EOF_SYMBOL;
start = pos;
}
}
}
// Check for include preprocessor directive
else if (*kind == T_STRING_LITERAL || *kind == T_ANGLE_STRING_LITERAL|| *kind == T_SLASH
|| (*kind == T_DOT && (tk.is(T_STRING_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL)))) {
bool include = false;
if (tokens.size() >= 3) {
if (tokens.at(0).is(T_POUND) && tokens.at(1).is(T_IDENTIFIER) && (tokens.at(2).is(T_STRING_LITERAL) ||
tokens.at(2).is(T_ANGLE_STRING_LITERAL))) {
const Token &directiveToken = tokens.at(1);
QString directive = tc.block().text().mid(directiveToken.utf16charsBegin(),
directiveToken.utf16chars());
if (directive == QLatin1String("include") ||
directive == QLatin1String("include_next") ||
directive == QLatin1String("import")) {
include = true;
}
}
}
if (!include) {
*kind = T_EOF_SYMBOL;
start = pos;
} else {
if (*kind == T_DOT) {
start = findStartOfName(start);
const QChar ch4 = start > -1 ? m_interface->characterAt(start - 1) : QChar();
const QChar ch5 = start > 0 ? m_interface->characterAt(start - 2) : QChar();
const QChar ch6 = start > 1 ? m_interface->characterAt(start - 3) : QChar();
start = start - CppCompletionAssistProvider::activationSequenceChar(ch4, ch5, ch6, kind, wantFunctionCall);
}
}
}
}
return start;
}
int CppCompletionAssistProcessor::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 CppCompletionAssistProcessor::startCompletionHelper()
{
if (m_languageFeatures.objCEnabled) {
if (tryObjCCompletion())
return m_startPosition;
}
const int startOfName = findStartOfName();
m_startPosition = startOfName;
m_model->m_completionOperator = T_EOF_SYMBOL;
int endOfOperator = m_startPosition;
// 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)), m_icons.keywordIcon());
return m_startPosition;
}
// Pre-processor completion
if (m_model->m_completionOperator == T_POUND) {
completePreprocessor();
m_startPosition = startOfName;
return m_startPosition;
}
// 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_startPosition = endOfExpression + 1;
return m_startPosition;
}
ExpressionUnderCursor expressionUnderCursor;
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_startPosition = 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_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_startPosition = 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 CppCompletionAssistProcessor::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);
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_startPosition = 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 CppCompletionAssistProcessor::addCompletionItem(const QString &text,
const QIcon &icon,
int order,
const QVariant &data)
{
BasicProposalItem *item = new CppAssistProposalItem;
item->setText(text);
item->setIcon(icon);
item->setOrder(order);
item->setData(data);
m_completions.append(item);
}
void CppCompletionAssistProcessor::addCompletionItem(CPlusPlus::Symbol *symbol,
int order)
{
ConvertToCompletionItem toCompletionItem;
BasicProposalItem *item = toCompletionItem(symbol);
if (item) {
item->setIcon(m_icons.iconForSymbol(symbol));
item->setOrder(order);
m_completions.append(item);
}
}
void CppCompletionAssistProcessor::completeObjCMsgSend(CPlusPlus::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 += TextEditor::Snippet::kVariableDelimiter;
text += QLatin1Char('(');
text += oo.prettyType(arg->type());
text += QLatin1Char(')');
text += oo.prettyName(arg->name());
text += TextEditor::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 CppCompletionAssistProcessor::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
ProjectPart::HeaderPaths headerPaths = m_interface->headerPaths();
const ProjectPart::HeaderPath currentFilePath(QFileInfo(m_interface->fileName()).path(),
ProjectPart::HeaderPath::IncludePath);
if (!headerPaths.contains(currentFilePath))
headerPaths.append(currentFilePath);
const Core::MimeType mimeType =
Core::MimeDatabase::findByType(QLatin1String("text/x-c++hdr"));
const QStringList suffixes = mimeType.suffixes();
foreach (const ProjectPart::HeaderPath &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 CppCompletionAssistProcessor::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, m_icons.keywordIcon());
}
}
}
void CppCompletionAssistProcessor::completePreprocessor()
{
foreach (const QString &preprocessorCompletion, 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 CppCompletionAssistProcessor::objcKeywordsWanted() const
{
if (!m_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
const QString mt = Core::MimeDatabase::findByFile(fileName).type();
return mt == QLatin1String(CppTools::Constants::OBJECTIVE_C_SOURCE_MIMETYPE)
|| mt == QLatin1String(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE);
}
2008-12-02 12:01:29 +01:00
int CppCompletionAssistProcessor::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);
globalCompletion(scope);
if (m_completions.isEmpty())
return -1;
return m_startPosition;
}
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);
2010-06-29 17:47:59 +02:00
ExpressionUnderCursor expressionUnderCursor;
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_startPosition;
break;
}
}
return -1;
} else {
// nothing to do.
return -1;
}
}
switch (m_model->m_completionOperator) {
case T_LPAREN:
if (completeConstructorOrFunction(results, endOfExpression, false))
return m_startPosition;
break;
case T_DOT:
case T_ARROW:
if (completeMember(results))
return m_startPosition;
break;
case T_COLON_COLON:
if (completeScope(results))
return m_startPosition;
break;
case T_SIGNAL:
if (completeSignal(results))
return m_startPosition;
break;
case T_SLOT:
if (completeSlot(results))
return m_startPosition;
break;
default:
break;
} // end of switch
// nothing to do.
return -1;
}
void CppCompletionAssistProcessor::globalCompletion(CPlusPlus::Scope *currentScope)
{
const LookupContext &context = m_model->m_typeOfExpression->context();
if (m_model->m_completionOperator == T_COLON_COLON) {
completeNamespace(context.globalNamespace());
return;
}
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;
}
}
for (; currentBinding; currentBinding = currentBinding->parent()) {
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(CppModelManagerInterface::configurationFileName(), context.snapshot());
addMacros(context.thisDocument()->fileName(), context.snapshot());
addSnippets();
2008-12-02 12:01:29 +01:00
}
bool CppCompletionAssistProcessor::completeMember(const QList<CPlusPlus::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->isObjCEnabled())
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 CppCompletionAssistProcessor::completeScope(const QList<CPlusPlus::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 CppCompletionAssistProcessor::completeNamespace(CPlusPlus::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);
2010-08-11 12:26:02 +02:00
for (Scope::iterator it = scope->firstMember(); it != scope->lastMember(); ++it) {
Symbol *member = *it;
addCompletionItem(member);
}
2008-12-02 12:01:29 +01:00
}
}
}
void CppCompletionAssistProcessor::completeClass(CPlusPlus::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 CppCompletionAssistProcessor::addClassMembersToCompletion(Scope *scope, bool staticLookup)
{
if (!scope)
return;
std::set<Class *> nestedAnonymouses;
for (Scope::iterator it = scope->firstMember(); it != scope->lastMember(); ++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 CppCompletionAssistProcessor::completeQtMethod(const QList<CPlusPlus::LookupItem> &results, bool wantSignals)
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 &p, results) {
FullySpecifiedType ty = p.type().simplified();
2008-12-02 12:01:29 +01:00
if (PointerType *ptrTy = ty->asPointerType())
2009-10-05 11:38:54 +02:00
ty = ptrTy->elementType().simplified();
2008-12-02 12:01:29 +01:00
else
continue; // not a pointer or a reference to a pointer.
NamedType *namedTy = ty->asNamedType();
if (!namedTy) // not a class name.
2008-12-02 12:01:29 +01:00
continue;
ClassOrNamespace *b = context.lookupType(namedTy->name(), p.scope());
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
}
foreach (Scope *scope, scopes) {
if (!scope->isClass())
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)
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 && !fun->isSlot())
2008-12-02 12:01:29 +01:00
continue;
unsigned count = fun->argumentCount();
while (true) {
QString signature;
signature += Overview().prettyName(fun->name());
signature += QLatin1Char('(');
for (unsigned i = 0; i < count; ++i) {
Symbol *arg = fun->argumentAt(i);
if (isQPrivateSignal(arg))
continue;
if (i != 0)
signature += QLatin1Char(',');
signature += o.prettyType(arg->type());
}
signature += QLatin1Char(')');
2008-12-02 12:01:29 +01:00
const QByteArray normalized =
QMetaObject::normalizedSignature(signature.toUtf8());
2008-12-02 12:01:29 +01:00
signature = QString::fromUtf8(normalized, normalized.size());
2008-12-02 12:01:29 +01:00
if (!signatures.contains(signature)) {
BasicProposalItem *ci = toCompletionItem(fun);
if (!ci)
2008-12-02 12:01:29 +01:00
break;
signatures.insert(signature);
ci->setText(signature); // fix the completion item.
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
}
void CppCompletionAssistProcessor::addSnippets()
2008-12-02 12:01:29 +01:00
{
m_completions.append(m_snippetCollector.collect());
}
void CppCompletionAssistProcessor::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)), m_icons.keywordIcon(), KeywordsOrder);
// primitive type completion items.
for (int i = T_FIRST_PRIMITIVE; i <= T_LAST_PRIMITIVE; ++i)
addCompletionItem(QLatin1String(Token::name(i)), m_icons.keywordIcon(), KeywordsOrder);
2008-12-02 12:01:29 +01:00
}
void CppCompletionAssistProcessor::addMacros(const QString &fileName, const CPlusPlus::Snapshot &snapshot)
{
QSet<QString> processed;
QSet<QString> definedMacros;
addMacros_helper(snapshot, fileName, &processed, &definedMacros);
foreach (const QString &macroName, definedMacros)
addCompletionItem(macroName, m_icons.macroIcon(), MacrosOrder);
}
void CppCompletionAssistProcessor::addMacros_helper(const CPlusPlus::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 CppCompletionAssistProcessor::completeConstructorOrFunction(const QList<CPlusPlus::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);
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",
QString completion = overview.prettyType(localTy).mid(1);
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;
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
if (BuiltinEditorDocumentParser *parser = BuiltinEditorDocumentParser::get(fileName())) {
parser->update(m_workingCopy);
m_snapshot = parser->snapshot();
m_headerPaths = parser->headerPaths();
}
}