forked from qt-creator/qt-creator
The code completion was keeping around references to old documents after the completion finished. This caused documents to stay in memory when unloading projects, up until the next time you used the completion.
1048 lines
35 KiB
C++
1048 lines
35 KiB
C++
/***************************************************************************
|
|
**
|
|
** This file is part of Qt Creator
|
|
**
|
|
** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
|
|
**
|
|
** Contact: Qt Software Information (qt-info@nokia.com)
|
|
**
|
|
**
|
|
** Non-Open Source Usage
|
|
**
|
|
** Licensees may use this file in accordance with the Qt Beta Version
|
|
** License Agreement, Agreement version 2.2 provided with the Software or,
|
|
** alternatively, in accordance with the terms contained in a written
|
|
** agreement between you and Nokia.
|
|
**
|
|
** GNU General Public License Usage
|
|
**
|
|
** Alternatively, this file may be used under the terms of the GNU General
|
|
** Public License versions 2.0 or 3.0 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL included in the packaging
|
|
** of this file. Please review the following information to ensure GNU
|
|
** General Public Licensing requirements will be met:
|
|
**
|
|
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
|
|
** http://www.gnu.org/copyleft/gpl.html.
|
|
**
|
|
** In addition, as a special exception, Nokia gives you certain additional
|
|
** rights. These rights are described in the Nokia Qt GPL Exception
|
|
** version 1.2, included in the file GPL_EXCEPTION.txt in this package.
|
|
**
|
|
***************************************************************************/
|
|
|
|
#include "cppcodecompletion.h"
|
|
#include "cppmodelmanager.h"
|
|
|
|
#include <Control.h>
|
|
#include <AST.h>
|
|
#include <ASTVisitor.h>
|
|
#include <CoreTypes.h>
|
|
#include <Literals.h>
|
|
#include <Names.h>
|
|
#include <NameVisitor.h>
|
|
#include <Symbols.h>
|
|
#include <SymbolVisitor.h>
|
|
#include <Scope.h>
|
|
#include <TranslationUnit.h>
|
|
#include <cplusplus/ResolveExpression.h>
|
|
#include <cplusplus/LookupContext.h>
|
|
#include <cplusplus/Overview.h>
|
|
#include <cplusplus/ExpressionUnderCursor.h>
|
|
#include <cplusplus/TokenUnderCursor.h>
|
|
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
#include <texteditor/itexteditor.h>
|
|
#include <texteditor/itexteditable.h>
|
|
#include <texteditor/basetexteditor.h>
|
|
|
|
#include <QtCore/QDebug>
|
|
#include <QtCore/QMap>
|
|
#include <QtCore/QFile>
|
|
#include <QtGui/QAction>
|
|
#include <QtGui/QKeyEvent>
|
|
#include <QtGui/QLabel>
|
|
#include <QtGui/QVBoxLayout>
|
|
#include <QtGui/QApplication>
|
|
|
|
using namespace CPlusPlus;
|
|
|
|
namespace CppTools {
|
|
namespace Internal {
|
|
|
|
class FunctionArgumentWidget : public QLabel {
|
|
public:
|
|
FunctionArgumentWidget(Core::ICore *core);
|
|
void showFunctionHint(Function *functionSymbol);
|
|
|
|
protected:
|
|
bool eventFilter(QObject *obj, QEvent *e);
|
|
|
|
private:
|
|
void update();
|
|
void close();
|
|
void updateHintText();
|
|
|
|
int m_startpos;
|
|
int m_currentarg;
|
|
|
|
TextEditor::ITextEditor *m_editor;
|
|
|
|
QFrame *m_popupFrame;
|
|
Function *m_item;
|
|
};
|
|
|
|
class ConvertToCompletionItem: protected NameVisitor
|
|
{
|
|
// The completion collector.
|
|
CppCodeCompletion *_collector;
|
|
|
|
// The completion item.
|
|
TextEditor::CompletionItem _item;
|
|
|
|
// The current symbol.
|
|
Symbol *_symbol;
|
|
|
|
// The pretty printer.
|
|
Overview overview;
|
|
|
|
public:
|
|
ConvertToCompletionItem(CppCodeCompletion *collector)
|
|
: _collector(collector),
|
|
_item(0),
|
|
_symbol(0)
|
|
{ }
|
|
|
|
TextEditor::CompletionItem operator()(Symbol *symbol)
|
|
{
|
|
if (! symbol || ! symbol->name() || symbol->name()->isQualifiedNameId())
|
|
return 0;
|
|
|
|
TextEditor::CompletionItem previousItem = switchCompletionItem(0);
|
|
Symbol *previousSymbol = switchSymbol(symbol);
|
|
accept(symbol->identity());
|
|
if (_item)
|
|
_item.m_data = QVariant::fromValue(symbol);
|
|
(void) switchSymbol(previousSymbol);
|
|
return switchCompletionItem(previousItem);
|
|
}
|
|
|
|
protected:
|
|
Symbol *switchSymbol(Symbol *symbol)
|
|
{
|
|
Symbol *previousSymbol = _symbol;
|
|
_symbol = symbol;
|
|
return previousSymbol;
|
|
}
|
|
|
|
TextEditor::CompletionItem switchCompletionItem(TextEditor::CompletionItem item)
|
|
{
|
|
TextEditor::CompletionItem previousItem = _item;
|
|
_item = item;
|
|
return previousItem;
|
|
}
|
|
|
|
TextEditor::CompletionItem newCompletionItem(Name *name)
|
|
{
|
|
TextEditor::CompletionItem item(_collector);
|
|
item.m_text = overview.prettyName(name);
|
|
item.m_icon = _collector->iconForSymbol(_symbol);
|
|
return item;
|
|
}
|
|
|
|
virtual void visit(NameId *name)
|
|
{ _item = newCompletionItem(name); }
|
|
|
|
virtual void visit(TemplateNameId *name)
|
|
{
|
|
_item = newCompletionItem(name);
|
|
_item.m_text = QLatin1String(name->identifier()->chars());
|
|
}
|
|
|
|
virtual void visit(DestructorNameId *name)
|
|
{ _item = newCompletionItem(name); }
|
|
|
|
virtual void visit(OperatorNameId *name)
|
|
{ _item = newCompletionItem(name); }
|
|
|
|
virtual void visit(ConversionNameId *name)
|
|
{ _item = newCompletionItem(name); }
|
|
|
|
virtual void visit(QualifiedNameId *name)
|
|
{ _item = newCompletionItem(name->unqualifiedNameId()); }
|
|
};
|
|
|
|
|
|
} // namespace Internal
|
|
} // namespace CppTools
|
|
|
|
|
|
|
|
using namespace CppTools::Internal;
|
|
|
|
FunctionArgumentWidget::FunctionArgumentWidget(Core::ICore *core)
|
|
: m_item(0)
|
|
{
|
|
QObject *editorObject = core->editorManager()->currentEditor();
|
|
m_editor = qobject_cast<TextEditor::ITextEditor *>(editorObject);
|
|
|
|
m_popupFrame = new QFrame(0, Qt::ToolTip|Qt::WindowStaysOnTopHint);
|
|
m_popupFrame->setFocusPolicy(Qt::NoFocus);
|
|
m_popupFrame->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
setFrameStyle(QFrame::Box);
|
|
setFrameShadow(QFrame::Plain);
|
|
|
|
setParent(m_popupFrame);
|
|
setFocusPolicy(Qt::NoFocus);
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout();
|
|
layout->addWidget(this);
|
|
layout->setMargin(0);
|
|
m_popupFrame->setLayout(layout);
|
|
|
|
QPalette pal = palette();
|
|
setAutoFillBackground(true);
|
|
pal.setColor(QPalette::Background, QColor(255, 255, 220));
|
|
setPalette(pal);
|
|
|
|
setTextFormat(Qt::RichText);
|
|
setMargin(1);
|
|
}
|
|
|
|
void FunctionArgumentWidget::showFunctionHint(Function *functionSymbol)
|
|
{
|
|
m_item = functionSymbol;
|
|
m_startpos = m_editor->position();
|
|
|
|
// update the text
|
|
m_currentarg = -1;
|
|
update();
|
|
|
|
QPoint pos = m_editor->cursorRect().topLeft();
|
|
pos.setY(pos.y() - sizeHint().height());
|
|
m_popupFrame->move(pos);
|
|
m_popupFrame->show();
|
|
|
|
QCoreApplication::instance()->installEventFilter(this);
|
|
}
|
|
|
|
void FunctionArgumentWidget::update()
|
|
{
|
|
int curpos = m_editor->position();
|
|
if (curpos < m_startpos) {
|
|
close();
|
|
return;
|
|
}
|
|
|
|
QString str = m_editor->textAt(m_startpos, curpos - m_startpos);
|
|
int argnr = 0;
|
|
int parcount = 0;
|
|
SimpleLexer tokenize;
|
|
QList<SimpleToken> tokens = tokenize(str);
|
|
for (int i = 0; i < tokens.count(); ++i) {
|
|
const SimpleToken &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 (m_currentarg != argnr) {
|
|
m_currentarg = argnr;
|
|
updateHintText();
|
|
}
|
|
|
|
if (parcount < 0)
|
|
close();
|
|
}
|
|
|
|
bool FunctionArgumentWidget::eventFilter(QObject *obj, QEvent *e)
|
|
{
|
|
switch (e->type()) {
|
|
case QEvent::KeyRelease:
|
|
{
|
|
if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
|
|
close();
|
|
return false;
|
|
}
|
|
update();
|
|
break;
|
|
}
|
|
case QEvent::WindowDeactivate:
|
|
case QEvent::Leave:
|
|
case QEvent::FocusOut:
|
|
{
|
|
if (obj != m_editor->widget())
|
|
break;
|
|
}
|
|
case QEvent::MouseButtonPress:
|
|
case QEvent::MouseButtonRelease:
|
|
case QEvent::MouseButtonDblClick:
|
|
case QEvent::Wheel:
|
|
close();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FunctionArgumentWidget::close()
|
|
{
|
|
m_popupFrame->close();
|
|
}
|
|
|
|
void FunctionArgumentWidget::updateHintText()
|
|
{
|
|
Overview overview;
|
|
overview.setShowReturnTypes(true);
|
|
overview.setShowArgumentNames(true);
|
|
overview.setMarkArgument(m_currentarg + 1);
|
|
QString text = overview(m_item->type(), m_item->name());
|
|
setText(text);
|
|
}
|
|
|
|
CppCodeCompletion::CppCodeCompletion(CppModelManager *manager, Core::ICore *core)
|
|
: ICompletionCollector(manager),
|
|
m_core(core),
|
|
m_manager(manager),
|
|
m_forcedCompletion(false),
|
|
m_completionOperator(T_EOF_SYMBOL)
|
|
{ }
|
|
|
|
QIcon CppCodeCompletion::iconForSymbol(Symbol *symbol) const
|
|
{ return m_icons.iconForSymbol(symbol); }
|
|
|
|
/*
|
|
Searches beckward for an access operator.
|
|
*/
|
|
static int startOfOperator(TextEditor::ITextEditable *editor,
|
|
int pos, unsigned *kind,
|
|
bool wantFunctionCall)
|
|
{
|
|
const QChar ch = pos > -1 ? editor->characterAt(pos - 1) : QChar();
|
|
const QChar ch2 = pos > 0 ? editor->characterAt(pos - 2) : QChar();
|
|
const QChar ch3 = pos > 1 ? editor->characterAt(pos - 3) : QChar();
|
|
|
|
int start = pos;
|
|
|
|
if (ch2 != QLatin1Char('.') && ch == QLatin1Char('.')) {
|
|
if (kind)
|
|
*kind = T_DOT;
|
|
--start;
|
|
} else if (wantFunctionCall && ch == QLatin1Char('(')) {
|
|
if (kind)
|
|
*kind = T_LPAREN;
|
|
--start;
|
|
} else if (ch2 == QLatin1Char(':') && ch == QLatin1Char(':')) {
|
|
if (kind)
|
|
*kind = T_COLON_COLON;
|
|
start -= 2;
|
|
} else if (ch2 == QLatin1Char('-') && ch == QLatin1Char('>')) {
|
|
if (kind)
|
|
*kind = T_ARROW;
|
|
start -= 2;
|
|
} else if (ch2 == QLatin1Char('.') && ch == QLatin1Char('*')) {
|
|
if (kind)
|
|
*kind = T_DOT_STAR;
|
|
start -= 2;
|
|
} else if (ch3 == QLatin1Char('-') && ch2 == QLatin1Char('>') && ch == QLatin1Char('*')) {
|
|
if (kind)
|
|
*kind = T_ARROW_STAR;
|
|
start -= 3;
|
|
}
|
|
|
|
if (start != pos) {
|
|
TextEditor::BaseTextEditor *edit = qobject_cast<TextEditor::BaseTextEditor *>(editor->widget());
|
|
QTextCursor tc(edit->textCursor());
|
|
tc.setPosition(pos);
|
|
static CPlusPlus::TokenUnderCursor tokenUnderCursor;
|
|
const SimpleToken tk = tokenUnderCursor(tc);
|
|
if (tk.is(T_COMMENT) || tk.isLiteral()) {
|
|
if (kind)
|
|
*kind = T_EOF_SYMBOL;
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
bool CppCodeCompletion::triggersCompletion(TextEditor::ITextEditable *editor)
|
|
{
|
|
if (! m_manager->isCppEditor(editor)) // ### remove me
|
|
return false;
|
|
|
|
const int pos = editor->position();
|
|
if (startOfOperator(editor, pos, /*token =*/ 0,
|
|
/*want function call=*/ true) != pos)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int CppCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
|
|
{
|
|
TextEditor::BaseTextEditor *edit = qobject_cast<TextEditor::BaseTextEditor *>(editor->widget());
|
|
if (! edit)
|
|
return -1;
|
|
|
|
m_editor = editor;
|
|
m_startPosition = findStartOfName(editor);
|
|
m_completionOperator = T_EOF_SYMBOL;
|
|
|
|
int endOfExpression = m_startPosition;
|
|
|
|
// Skip whitespace preceding this position
|
|
while (editor->characterAt(endOfExpression - 1).isSpace())
|
|
--endOfExpression;
|
|
|
|
endOfExpression = startOfOperator(editor, endOfExpression,
|
|
&m_completionOperator,
|
|
/*want function call =*/ editor->position() == endOfExpression);
|
|
|
|
Core::IFile *file = editor->file();
|
|
QString fileName = file->fileName();
|
|
|
|
int line = 0, column = 0;
|
|
edit->convertPosition(editor->position(), &line, &column);
|
|
// qDebug() << "line:" << line << "column:" << column;
|
|
|
|
ExpressionUnderCursor expressionUnderCursor;
|
|
QString expression;
|
|
|
|
if (m_completionOperator) {
|
|
QTextCursor tc(edit->document());
|
|
tc.setPosition(endOfExpression);
|
|
expression = expressionUnderCursor(tc);
|
|
if (m_completionOperator == T_LPAREN) {
|
|
if (expression.endsWith(QLatin1String("SIGNAL")))
|
|
m_completionOperator = T_SIGNAL;
|
|
else if (expression.endsWith(QLatin1String("SLOT")))
|
|
m_completionOperator = T_SLOT;
|
|
}
|
|
}
|
|
|
|
//if (! expression.isEmpty())
|
|
//qDebug() << "***** expression:" << expression;
|
|
|
|
if (Document::Ptr thisDocument = m_manager->document(fileName)) {
|
|
Symbol *symbol = thisDocument->findSymbolAt(line, column);
|
|
|
|
typeOfExpression.setDocuments(m_manager->documents());
|
|
|
|
QList<TypeOfExpression::Result> resolvedTypes = typeOfExpression(expression, thisDocument, symbol);
|
|
LookupContext context = typeOfExpression.lookupContext();
|
|
|
|
if (!typeOfExpression.expressionAST() && (! m_completionOperator ||
|
|
m_completionOperator == T_COLON_COLON)) {
|
|
if (!m_completionOperator) {
|
|
addKeywords();
|
|
addMacros(context);
|
|
}
|
|
|
|
const QList<Scope *> scopes = context.expand(context.visibleScopes());
|
|
foreach (Scope *scope, scopes) {
|
|
for (unsigned i = 0; i < scope->symbolCount(); ++i) {
|
|
addCompletionItem(scope->symbolAt(i));
|
|
}
|
|
}
|
|
return m_startPosition;
|
|
}
|
|
|
|
// qDebug() << "found" << resolvedTypes.count() << "symbols for expression:" << expression;
|
|
|
|
if (resolvedTypes.isEmpty() && (m_completionOperator == T_SIGNAL ||
|
|
m_completionOperator == T_SLOT)) {
|
|
// Apply signal/slot completion on 'this'
|
|
expression = QLatin1String("this");
|
|
resolvedTypes = typeOfExpression(expression, thisDocument, symbol);
|
|
context = typeOfExpression.lookupContext();
|
|
}
|
|
|
|
if (! resolvedTypes.isEmpty()) {
|
|
FullySpecifiedType exprTy = resolvedTypes.first().first;
|
|
|
|
if (exprTy->isReferenceType())
|
|
exprTy = exprTy->asReferenceType()->elementType();
|
|
|
|
if (m_completionOperator == T_LPAREN && completeFunction(exprTy, resolvedTypes, context)) {
|
|
return m_startPosition;
|
|
} if ((m_completionOperator == T_DOT || m_completionOperator == T_ARROW) &&
|
|
completeMember(exprTy, resolvedTypes, context)) {
|
|
return m_startPosition;
|
|
} else if (m_completionOperator == T_COLON_COLON && completeScope(exprTy, resolvedTypes, context)) {
|
|
return m_startPosition;
|
|
} else if (m_completionOperator == T_SIGNAL && completeSignal(exprTy, resolvedTypes, context)) {
|
|
return m_startPosition;
|
|
} else if (m_completionOperator == T_SLOT && completeSlot(exprTy, resolvedTypes, context)) {
|
|
return m_startPosition;
|
|
}
|
|
}
|
|
}
|
|
|
|
// nothing to do.
|
|
return -1;
|
|
}
|
|
|
|
bool CppCodeCompletion::completeFunction(FullySpecifiedType exprTy,
|
|
const QList<TypeOfExpression::Result> &resolvedTypes,
|
|
const LookupContext &)
|
|
{
|
|
ConvertToCompletionItem toCompletionItem(this);
|
|
Overview o;
|
|
o.setShowReturnTypes(true);
|
|
o.setShowArgumentNames(true);
|
|
|
|
if (Class *klass = exprTy->asClass()) {
|
|
for (unsigned i = 0; i < klass->memberCount(); ++i) {
|
|
Symbol *member = klass->memberAt(i);
|
|
if (! member->type()->isFunction())
|
|
continue;
|
|
else if (! member->identity())
|
|
continue;
|
|
else if (! member->identity()->isEqualTo(klass->identity()))
|
|
continue;
|
|
if (TextEditor::CompletionItem item = toCompletionItem(member)) {
|
|
item.m_text = o(member->type(), member->name());
|
|
m_completions.append(item);
|
|
}
|
|
}
|
|
} else {
|
|
QSet<QString> signatures;
|
|
foreach (TypeOfExpression::Result p, resolvedTypes) {
|
|
FullySpecifiedType ty = p.first;
|
|
if (Function *fun = ty->asFunction()) {
|
|
if (TextEditor::CompletionItem item = toCompletionItem(fun)) {
|
|
QString signature;
|
|
signature += overview.prettyName(fun->name());
|
|
signature += overview.prettyType(fun->type());
|
|
if (signatures.contains(signature))
|
|
continue;
|
|
signatures.insert(signature);
|
|
|
|
item.m_text = o(ty, fun->name());
|
|
m_completions.append(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ! m_completions.isEmpty();
|
|
}
|
|
|
|
bool CppCodeCompletion::completeMember(FullySpecifiedType,
|
|
const QList<TypeOfExpression::Result> &results,
|
|
const LookupContext &context)
|
|
{
|
|
Q_ASSERT(! results.isEmpty());
|
|
|
|
QList<Symbol *> classObjectCandidates;
|
|
|
|
TypeOfExpression::Result p = results.first();
|
|
|
|
if (m_completionOperator == T_ARROW) {
|
|
FullySpecifiedType ty = p.first;
|
|
|
|
if (ReferenceType *refTy = ty->asReferenceType())
|
|
ty = refTy->elementType();
|
|
|
|
if (NamedType *namedTy = ty->asNamedType()) {
|
|
ResolveExpression resolveExpression(context);
|
|
|
|
Name *className = namedTy->name();
|
|
const QList<Symbol *> candidates =
|
|
context.resolveClass(className, context.visibleScopes(p));
|
|
|
|
foreach (Symbol *classObject, candidates) {
|
|
const QList<TypeOfExpression::Result> overloads =
|
|
resolveExpression.resolveArrowOperator(p, namedTy,
|
|
classObject->asClass());
|
|
|
|
foreach (TypeOfExpression::Result r, overloads) {
|
|
FullySpecifiedType ty = r.first;
|
|
Function *funTy = ty->asFunction();
|
|
if (! funTy)
|
|
continue;
|
|
|
|
ty = funTy->returnType();
|
|
|
|
if (ReferenceType *refTy = ty->asReferenceType())
|
|
ty = refTy->elementType();
|
|
|
|
if (PointerType *ptrTy = ty->asPointerType()) {
|
|
if (NamedType *namedTy = ptrTy->elementType()->asNamedType()) {
|
|
const QList<Symbol *> classes =
|
|
context.resolveClass(namedTy->name(),
|
|
context.visibleScopes(p));
|
|
|
|
foreach (Symbol *c, classes) {
|
|
if (! classObjectCandidates.contains(c))
|
|
classObjectCandidates.append(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (PointerType *ptrTy = ty->asPointerType()) {
|
|
if (NamedType *namedTy = ptrTy->elementType()->asNamedType()) {
|
|
const QList<Symbol *> classes =
|
|
context.resolveClass(namedTy->name(),
|
|
context.visibleScopes(p));
|
|
|
|
foreach (Symbol *c, classes) {
|
|
if (! classObjectCandidates.contains(c))
|
|
classObjectCandidates.append(c);
|
|
}
|
|
}
|
|
}
|
|
} else if (m_completionOperator == T_DOT) {
|
|
FullySpecifiedType ty = p.first;
|
|
|
|
if (ReferenceType *refTy = ty->asReferenceType())
|
|
ty = refTy->elementType();
|
|
|
|
NamedType *namedTy = 0;
|
|
if (PointerType *ptrTy = ty->asPointerType()) {
|
|
// Replace . with ->
|
|
int length = m_editor->position() - m_startPosition + 1;
|
|
m_editor->setCurPos(m_startPosition - 1);
|
|
m_editor->replace(length, QLatin1String("->"));
|
|
m_startPosition++;
|
|
namedTy = ptrTy->elementType()->asNamedType();
|
|
} else {
|
|
namedTy = ty->asNamedType();
|
|
if (! namedTy) {
|
|
Function *fun = ty->asFunction();
|
|
if (fun && (fun->scope()->isBlockScope() || fun->scope()->isNamespaceScope()))
|
|
namedTy = fun->returnType()->asNamedType();
|
|
}
|
|
}
|
|
|
|
if (namedTy) {
|
|
const QList<Symbol *> classes =
|
|
context.resolveClass(namedTy->name(),
|
|
context.visibleScopes(p));
|
|
|
|
foreach (Symbol *c, classes) {
|
|
if (! classObjectCandidates.contains(c))
|
|
classObjectCandidates.append(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
completeClass(classObjectCandidates, context, /*static lookup = */ false);
|
|
if (! m_completions.isEmpty())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CppCodeCompletion::completeScope(FullySpecifiedType exprTy,
|
|
const QList<TypeOfExpression::Result> &resolvedTypes,
|
|
const LookupContext &context)
|
|
{
|
|
// Search for a class or a namespace.
|
|
foreach (TypeOfExpression::Result p, resolvedTypes) {
|
|
if (p.first->isClass() || p.first->isNamespace()) {
|
|
exprTy = p.first;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (exprTy->asNamespace()) {
|
|
QList<Symbol *> candidates;
|
|
foreach (TypeOfExpression::Result p, resolvedTypes) {
|
|
if (Namespace *ns = p.first->asNamespace())
|
|
candidates.append(ns);
|
|
}
|
|
completeNamespace(candidates, context);
|
|
} else if (exprTy->isClass()) {
|
|
QList<Symbol *> candidates;
|
|
foreach (TypeOfExpression::Result p, resolvedTypes) {
|
|
if (Class *k = p.first->asClass())
|
|
candidates.append(k);
|
|
}
|
|
completeClass(candidates, context);
|
|
}
|
|
|
|
return ! m_completions.isEmpty();
|
|
}
|
|
|
|
void CppCodeCompletion::addKeywords()
|
|
{
|
|
// keyword completion items.
|
|
for (int i = T_FIRST_KEYWORD; i < T_FIRST_QT_KEYWORD; ++i) {
|
|
TextEditor::CompletionItem item(this);
|
|
item.m_text = QLatin1String(Token::name(i));
|
|
item.m_icon = m_icons.keywordIcon();
|
|
m_completions.append(item);
|
|
}
|
|
}
|
|
|
|
void CppCodeCompletion::addMacros(const LookupContext &context)
|
|
{
|
|
// macro completion items.
|
|
QSet<QByteArray> macroNames;
|
|
QSet<QString> processed;
|
|
QList<QString> todo;
|
|
todo.append(context.thisDocument()->fileName());
|
|
while (! todo.isEmpty()) {
|
|
QString fn = todo.last();
|
|
todo.removeLast();
|
|
if (processed.contains(fn))
|
|
continue;
|
|
processed.insert(fn);
|
|
if (Document::Ptr doc = context.document(fn)) {
|
|
foreach (const Macro macro, doc->definedMacros()) {
|
|
macroNames.insert(macro.name);
|
|
}
|
|
todo += doc->includedFiles();
|
|
}
|
|
}
|
|
|
|
foreach (const QByteArray macroName, macroNames) {
|
|
TextEditor::CompletionItem item(this);
|
|
item.m_text = QString::fromLatin1(macroName.constData(), macroName.length());
|
|
item.m_icon = m_icons.macroIcon();
|
|
m_completions.append(item);
|
|
}
|
|
}
|
|
|
|
void CppCodeCompletion::addCompletionItem(Symbol *symbol)
|
|
{
|
|
ConvertToCompletionItem toCompletionItem(this);
|
|
if (TextEditor::CompletionItem item = toCompletionItem(symbol))
|
|
m_completions.append(item);
|
|
}
|
|
|
|
void CppCodeCompletion::completeNamespace(const QList<Symbol *> &candidates,
|
|
const LookupContext &context)
|
|
{
|
|
QList<Scope *> todo;
|
|
QList<Scope *> visibleScopes = context.visibleScopes();
|
|
foreach (Symbol *candidate, candidates) {
|
|
if (Namespace *ns = candidate->asNamespace())
|
|
context.expand(ns->members(), visibleScopes, &todo);
|
|
}
|
|
|
|
foreach (Scope *scope, todo) {
|
|
addCompletionItem(scope->owner());
|
|
|
|
for (unsigned i = 0; i < scope->symbolCount(); ++i) {
|
|
addCompletionItem(scope->symbolAt(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CppCodeCompletion::completeClass(const QList<Symbol *> &candidates,
|
|
const LookupContext &context,
|
|
bool staticLookup)
|
|
{
|
|
if (candidates.isEmpty())
|
|
return;
|
|
|
|
Class *klass = candidates.first()->asClass();
|
|
|
|
QList<Scope *> todo;
|
|
context.expand(klass->members(), context.visibleScopes(), &todo);
|
|
|
|
foreach (Scope *scope, todo) {
|
|
addCompletionItem(scope->owner());
|
|
|
|
for (unsigned i = 0; i < scope->symbolCount(); ++i) {
|
|
Symbol *symbol = scope->symbolAt(i);
|
|
|
|
if (symbol->type().isFriend())
|
|
continue;
|
|
else if (! staticLookup && (symbol->isTypedef() ||
|
|
symbol->isEnum() ||
|
|
symbol->isClass()))
|
|
continue;
|
|
|
|
addCompletionItem(symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CppCodeCompletion::completeQtMethod(CPlusPlus::FullySpecifiedType,
|
|
const QList<TypeOfExpression::Result> &results,
|
|
const LookupContext &context,
|
|
bool wantSignals)
|
|
{
|
|
if (results.isEmpty())
|
|
return false;
|
|
|
|
ConvertToCompletionItem toCompletionItem(this);
|
|
Overview o;
|
|
o.setShowReturnTypes(false);
|
|
o.setShowArgumentNames(false);
|
|
o.setShowFunctionSignatures(true);
|
|
|
|
QSet<QString> signatures;
|
|
foreach (TypeOfExpression::Result p, results) {
|
|
FullySpecifiedType ty = p.first;
|
|
if (ReferenceType *refTy = ty->asReferenceType())
|
|
ty = refTy->elementType();
|
|
if (PointerType *ptrTy = ty->asPointerType())
|
|
ty = ptrTy->elementType();
|
|
else
|
|
continue; // not a pointer or a reference to a pointer.
|
|
|
|
NamedType *namedTy = ty->asNamedType();
|
|
if (! namedTy) // not a class name.
|
|
continue;
|
|
|
|
const QList<Scope *> visibleScopes = context.visibleScopes(p);
|
|
|
|
const QList<Symbol *> classObjects =
|
|
context.resolveClass(namedTy->name(), visibleScopes);
|
|
|
|
if (classObjects.isEmpty())
|
|
continue;
|
|
|
|
Class *klass = classObjects.first()->asClass();
|
|
|
|
QList<Scope *> todo;
|
|
context.expand(klass->members(), visibleScopes, &todo);
|
|
|
|
foreach (Scope *scope, todo) {
|
|
if (! scope->isClassScope())
|
|
continue;
|
|
|
|
for (unsigned i = 0; i < scope->symbolCount(); ++i) {
|
|
Symbol *member = scope->symbolAt(i);
|
|
Function *fun = member->type()->asFunction();
|
|
if (! fun)
|
|
continue;
|
|
if (wantSignals && ! fun->isSignal())
|
|
continue;
|
|
else if (! wantSignals && ! fun->isSlot())
|
|
continue;
|
|
if (TextEditor::CompletionItem item = toCompletionItem(fun)) {
|
|
unsigned count = fun->argumentCount();
|
|
while (true) {
|
|
TextEditor::CompletionItem i = item;
|
|
|
|
QString signature;
|
|
signature += overview.prettyName(fun->name());
|
|
signature += QLatin1Char('(');
|
|
for (unsigned i = 0; i < count; ++i) {
|
|
Symbol *arg = fun->argumentAt(i);
|
|
if (i != 0)
|
|
signature += QLatin1Char(',');
|
|
signature += o.prettyType(arg->type());
|
|
}
|
|
signature += QLatin1Char(')');
|
|
|
|
const QByteArray normalized =
|
|
QMetaObject::normalizedSignature(signature.toLatin1());
|
|
|
|
signature = QString::fromLatin1(normalized, normalized.size());
|
|
|
|
if (! signatures.contains(signature)) {
|
|
signatures.insert(signature);
|
|
|
|
i.m_text = signature; // fix the completion item.
|
|
m_completions.append(i);
|
|
}
|
|
|
|
if (count && fun->argumentAt(count - 1)->asArgument()->hasInitializer())
|
|
--count;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ! m_completions.isEmpty();
|
|
}
|
|
|
|
void CppCodeCompletion::completions(QList<TextEditor::CompletionItem> *completions)
|
|
{
|
|
const int length = m_editor->position() - m_startPosition;
|
|
|
|
if (length == 0)
|
|
*completions = m_completions;
|
|
else if (length > 0) {
|
|
const QString key = m_editor->textAt(m_startPosition, length);
|
|
|
|
if (m_completionOperator != T_LPAREN) {
|
|
/*
|
|
* This code builds a regular expression in order to more intelligently match
|
|
* camel-case style. This means upper-case characters will be rewritten as follows:
|
|
*
|
|
* A => [a-z0-9_]*A (for any but the first capital letter)
|
|
*
|
|
* Meaning it allows any sequence of lower-case characters to preceed an
|
|
* upper-case character. So for example gAC matches getActionController.
|
|
*
|
|
* The match is case-sensitive as soon as at least one upper-case character is
|
|
* present.
|
|
*/
|
|
QString keyRegExp;
|
|
keyRegExp += QLatin1Char('^');
|
|
bool first = true;
|
|
Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive;
|
|
foreach (const QChar &c, key) {
|
|
if (c.isLower()) {
|
|
keyRegExp.append(c);
|
|
} else if (c.isUpper()) {
|
|
sensitivity = Qt::CaseSensitive;
|
|
if (!first) {
|
|
keyRegExp.append("[a-z0-9_]*");
|
|
}
|
|
keyRegExp.append(c);
|
|
} else {
|
|
keyRegExp.append(QRegExp::escape(c));
|
|
}
|
|
first = false;
|
|
}
|
|
const QRegExp regExp(keyRegExp, sensitivity);
|
|
|
|
foreach (TextEditor::CompletionItem item, m_completions) {
|
|
if (regExp.indexIn(item.m_text) == 0) {
|
|
item.m_relevance = (key.length() > 0 &&
|
|
item.m_text.startsWith(key, Qt::CaseInsensitive)) ? 1 : 0;
|
|
(*completions) << item;
|
|
}
|
|
}
|
|
} else if (m_completionOperator == T_LPAREN ||
|
|
m_completionOperator == T_SIGNAL ||
|
|
m_completionOperator == T_SLOT) {
|
|
foreach (TextEditor::CompletionItem item, m_completions) {
|
|
if (item.m_text.startsWith(key, Qt::CaseInsensitive)) {
|
|
(*completions) << item;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CppCodeCompletion::complete(const TextEditor::CompletionItem &item)
|
|
{
|
|
Symbol *symbol = 0;
|
|
|
|
if (item.m_data.isValid())
|
|
symbol = item.m_data.value<Symbol *>();
|
|
|
|
// qDebug() << "*** complete symbol:" << symbol->fileName() << symbol->line();
|
|
|
|
if (m_completionOperator == T_LPAREN) {
|
|
if (symbol) {
|
|
Function *function = symbol->type()->asFunction();
|
|
Q_ASSERT(function != 0);
|
|
|
|
m_functionArgumentWidget = new FunctionArgumentWidget(m_core);
|
|
m_functionArgumentWidget->showFunctionHint(function);
|
|
}
|
|
} else if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
|
|
QString toInsert = item.m_text;
|
|
toInsert += QLatin1Char(')');
|
|
// Insert the remainder of the name
|
|
int length = m_editor->position() - m_startPosition;
|
|
m_editor->setCurPos(m_startPosition);
|
|
m_editor->replace(length, toInsert);
|
|
} else {
|
|
QString toInsert = item.m_text;
|
|
|
|
//qDebug() << "current symbol:" << overview.prettyName(symbol->name())
|
|
//<< overview.prettyType(symbol->type());
|
|
|
|
if (symbol) {
|
|
if (Function *function = symbol->type()->asFunction()) {
|
|
// If the member is a function, automatically place the opening parenthesis,
|
|
// except when it might take template parameters.
|
|
if (!function->returnType().isValid()
|
|
&& (function->identity() && !function->identity()->isDestructorNameId())) {
|
|
// Don't insert any magic, since the user might have just wanted to select the class
|
|
|
|
} else if (function->templateParameterCount() != 0) {
|
|
// If there are no arguments, then we need the template specification
|
|
if (function->argumentCount() == 0) {
|
|
toInsert.append(QLatin1Char('<'));
|
|
}
|
|
} else {
|
|
toInsert.append(QLatin1Char('('));
|
|
|
|
// If the function takes no arguments, automatically place the closing parenthesis
|
|
if (function->argumentCount() == 0 || (function->argumentCount() == 1 &&
|
|
function->argumentAt(0)->type()->isVoidType())) {
|
|
toInsert.append(QLatin1Char(')'));
|
|
|
|
// If the function doesn't return anything, automatically place the semicolon,
|
|
// unless we're doing a scope completion (then it might be function definition).
|
|
if (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON) {
|
|
toInsert.append(QLatin1Char(';'));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Insert the remainder of the name
|
|
int length = m_editor->position() - m_startPosition;
|
|
m_editor->setCurPos(m_startPosition);
|
|
m_editor->replace(length, toInsert);
|
|
}
|
|
}
|
|
|
|
bool CppCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems)
|
|
{
|
|
if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
|
|
return false;
|
|
} else if (completionItems.count() == 1) {
|
|
complete(completionItems.first());
|
|
return true;
|
|
} else if (m_completionOperator != T_LPAREN) {
|
|
// Compute common prefix
|
|
QString firstKey = completionItems.first().m_text;
|
|
QString lastKey = completionItems.last().m_text;
|
|
const int length = qMin(firstKey.length(), lastKey.length());
|
|
firstKey.truncate(length);
|
|
lastKey.truncate(length);
|
|
|
|
while (firstKey != lastKey) {
|
|
firstKey.chop(1);
|
|
lastKey.chop(1);
|
|
}
|
|
|
|
int typedLength = m_editor->position() - m_startPosition;
|
|
if (!firstKey.isEmpty() && firstKey.length() > typedLength) {
|
|
m_editor->setCurPos(m_startPosition);
|
|
m_editor->replace(typedLength, firstKey);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CppCodeCompletion::cleanup()
|
|
{
|
|
m_completions.clear();
|
|
|
|
// Set empty map in order to avoid referencing old versions of the documents
|
|
// until the next completion
|
|
typeOfExpression.setDocuments(QMap<QString, Document::Ptr>());
|
|
}
|
|
|
|
int CppCodeCompletion::findStartOfName(const TextEditor::ITextEditor *editor)
|
|
{
|
|
int pos = editor->position();
|
|
QChar chr;
|
|
|
|
// Skip to the start of a name
|
|
do {
|
|
chr = editor->characterAt(--pos);
|
|
} while (chr.isLetterOrNumber() || chr == QLatin1Char('_'));
|
|
|
|
return pos + 1;
|
|
}
|