diff options
Diffstat (limited to 'src/plugins/cpptools/cppcodecompletion.cpp')
-rw-r--r-- | src/plugins/cpptools/cppcodecompletion.cpp | 1040 |
1 files changed, 1040 insertions, 0 deletions
diff --git a/src/plugins/cpptools/cppcodecompletion.cpp b/src/plugins/cpptools/cppcodecompletion.cpp new file mode 100644 index 0000000000..5950477184 --- /dev/null +++ b/src/plugins/cpptools/cppcodecompletion.cpp @@ -0,0 +1,1040 @@ +/*************************************************************************** +** +** 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)) { + macroNames += doc->macroNames(); + 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(); +} + +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; +} |