1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "cppcompletionassist.h"
27
28 #include "builtineditordocumentparser.h"
29 #include "cppdoxygen.h"
30 #include "cppmodelmanager.h"
31 #include "cpptoolsconstants.h"
32 #include "cpptoolsreuse.h"
33 #include "editordocumenthandle.h"
34
35 #include <coreplugin/icore.h>
36 #include <cppeditor/cppeditorconstants.h>
37 #include <texteditor/codeassist/assistproposalitem.h>
38 #include <texteditor/codeassist/genericproposal.h>
39 #include <texteditor/codeassist/ifunctionhintproposalmodel.h>
40 #include <texteditor/codeassist/functionhintproposal.h>
41 #include <texteditor/snippets/snippet.h>
42 #include <texteditor/texteditorsettings.h>
43 #include <texteditor/completionsettings.h>
44
45 #include <utils/textutils.h>
46 #include <utils/mimetypes/mimedatabase.h>
47 #include <utils/qtcassert.h>
48
49 #include <cplusplus/BackwardsScanner.h>
50 #include <cplusplus/CppRewriter.h>
51 #include <cplusplus/ExpressionUnderCursor.h>
52 #include <cplusplus/MatchingText.h>
53 #include <cplusplus/Overview.h>
54 #include <cplusplus/ResolveExpression.h>
55
56 #include <QDirIterator>
57 #include <QLatin1String>
58 #include <QTextCursor>
59 #include <QTextDocument>
60 #include <QIcon>
61
62 using namespace CPlusPlus;
63 using namespace CppEditor;
64 using namespace CppTools;
65 using namespace CppTools::Internal;
66 using namespace TextEditor;
67
68 namespace CppTools {
69 namespace Internal {
70
71 struct CompleteFunctionDeclaration
72 {
CompleteFunctionDeclarationCppTools::Internal::CompleteFunctionDeclaration73 explicit CompleteFunctionDeclaration(Function *f = nullptr)
74 : function(f)
75 {}
76
77 Function *function;
78 };
79
80 // ---------------------
81 // CppAssistProposalItem
82 // ---------------------
83 class CppAssistProposalItem final : public AssistProposalItem
84 {
85 public:
86 ~CppAssistProposalItem() noexcept override = default;
87 bool prematurelyApplies(const QChar &c) const override;
88 void applyContextualContent(TextDocumentManipulatorInterface &manipulator, int basePosition) const override;
89
isOverloaded() const90 bool isOverloaded() const { return m_isOverloaded; }
markAsOverloaded()91 void markAsOverloaded() { m_isOverloaded = true; }
keepCompletionOperator(unsigned compOp)92 void keepCompletionOperator(unsigned compOp) { m_completionOperator = compOp; }
keepTypeOfExpression(const QSharedPointer<TypeOfExpression> & typeOfExp)93 void keepTypeOfExpression(const QSharedPointer<TypeOfExpression> &typeOfExp)
94 { m_typeOfExpression = typeOfExp; }
isKeyword() const95 bool isKeyword() const final
96 { return m_isKeyword; }
setIsKeyword(bool isKeyword)97 void setIsKeyword(bool isKeyword)
98 { m_isKeyword = isKeyword; }
99
100 quint64 hash() const override;
101
102 private:
103 QSharedPointer<TypeOfExpression> m_typeOfExpression;
104 unsigned m_completionOperator = T_EOF_SYMBOL;
105 mutable QChar m_typedChar;
106 bool m_isOverloaded = false;
107 bool m_isKeyword = false;
108 };
109
110 } // Internal
111 } // CppTools
112
Q_DECLARE_METATYPE(CppTools::Internal::CompleteFunctionDeclaration)113 Q_DECLARE_METATYPE(CppTools::Internal::CompleteFunctionDeclaration)
114
115 bool CppAssistProposalModel::isSortable(const QString &prefix) const
116 {
117 if (m_completionOperator != T_EOF_SYMBOL)
118 return true;
119
120 return !prefix.isEmpty();
121 }
122
proposalItem(int index) const123 AssistProposalItemInterface *CppAssistProposalModel::proposalItem(int index) const
124 {
125 AssistProposalItemInterface *item = GenericProposalModel::proposalItem(index);
126 if (!item->isSnippet()) {
127 auto cppItem = static_cast<CppAssistProposalItem *>(item);
128 cppItem->keepCompletionOperator(m_completionOperator);
129 cppItem->keepTypeOfExpression(m_typeOfExpression);
130 }
131 return item;
132 }
133
prematurelyApplies(const QChar & typedChar) const134 bool CppAssistProposalItem::prematurelyApplies(const QChar &typedChar) const
135 {
136 if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
137 if (typedChar == QLatin1Char('(') || typedChar == QLatin1Char(',')) {
138 m_typedChar = typedChar;
139 return true;
140 }
141 } else if (m_completionOperator == T_STRING_LITERAL
142 || m_completionOperator == T_ANGLE_STRING_LITERAL) {
143 if (typedChar == QLatin1Char('/') && text().endsWith(QLatin1Char('/'))) {
144 m_typedChar = typedChar;
145 return true;
146 }
147 } else if (data().value<Symbol *>()) {
148 if (typedChar == QLatin1Char(':')
149 || typedChar == QLatin1Char(';')
150 || typedChar == QLatin1Char('.')
151 || typedChar == QLatin1Char(',')
152 || typedChar == QLatin1Char('(')) {
153 m_typedChar = typedChar;
154 return true;
155 }
156 } else if (data().canConvert<CompleteFunctionDeclaration>()) {
157 if (typedChar == QLatin1Char('(')) {
158 m_typedChar = typedChar;
159 return true;
160 }
161 }
162
163 return false;
164 }
165
isDereferenced(TextDocumentManipulatorInterface & manipulator,int basePosition)166 static bool isDereferenced(TextDocumentManipulatorInterface &manipulator, int basePosition)
167 {
168 QTextCursor cursor = manipulator.textCursorAt(basePosition);
169 cursor.setPosition(basePosition);
170
171 BackwardsScanner scanner(cursor, LanguageFeatures());
172 for (int pos = scanner.startToken()-1; pos >= 0; pos--) {
173 switch (scanner[pos].kind()) {
174 case T_COLON_COLON:
175 case T_IDENTIFIER:
176 //Ignore scope specifiers
177 break;
178
179 case T_AMPER: return true;
180 default: return false;
181 }
182 }
183 return false;
184 }
185
hash() const186 quint64 CppAssistProposalItem::hash() const
187 {
188 if (data().canConvert<Symbol *>())
189 return quint64(data().value<Symbol *>()->index());
190 else if (data().canConvert<CompleteFunctionDeclaration>())
191 return quint64(data().value<CompleteFunctionDeclaration>().function->index());
192
193 return 0;
194 }
195
applyContextualContent(TextDocumentManipulatorInterface & manipulator,int basePosition) const196 void CppAssistProposalItem::applyContextualContent(TextDocumentManipulatorInterface &manipulator, int basePosition) const
197 {
198 Symbol *symbol = nullptr;
199
200 if (data().isValid())
201 symbol = data().value<Symbol *>();
202
203 QString toInsert;
204 QString extraChars;
205 int extraLength = 0;
206 int cursorOffset = 0;
207 bool setAutoCompleteSkipPos = false;
208
209 bool autoParenthesesEnabled = true;
210
211 if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
212 toInsert = text();
213 extraChars += QLatin1Char(')');
214
215 if (m_typedChar == QLatin1Char('(')) // Eat the opening parenthesis
216 m_typedChar = QChar();
217 } else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) {
218 toInsert = text();
219 if (!toInsert.endsWith(QLatin1Char('/'))) {
220 extraChars += QLatin1Char((m_completionOperator == T_ANGLE_STRING_LITERAL) ? '>' : '"');
221 } else {
222 if (m_typedChar == QLatin1Char('/')) // Eat the slash
223 m_typedChar = QChar();
224 }
225 } else {
226 toInsert = text();
227
228 const CompletionSettings &completionSettings = TextEditorSettings::completionSettings();
229 const bool autoInsertBrackets = completionSettings.m_autoInsertBrackets;
230
231 if (autoInsertBrackets && symbol && symbol->type()) {
232 if (Function *function = symbol->type()->asFunctionType()) {
233 // If the member is a function, automatically place the opening parenthesis,
234 // except when it might take template parameters.
235 if (!function->hasReturnType()
236 && (function->unqualifiedName()
237 && !function->unqualifiedName()->isDestructorNameId())) {
238 // Don't insert any magic, since the user might have just wanted to select the class
239
240 /// ### port me
241 #if 0
242 } else if (function->templateParameterCount() != 0 && typedChar != QLatin1Char('(')) {
243 // If there are no arguments, then we need the template specification
244 if (function->argumentCount() == 0)
245 extraChars += QLatin1Char('<');
246 #endif
247 } else if (!isDereferenced(manipulator, basePosition) && !function->isAmbiguous()) {
248 // When the user typed the opening parenthesis, he'll likely also type the closing one,
249 // in which case it would be annoying if we put the cursor after the already automatically
250 // inserted closing parenthesis.
251 const bool skipClosingParenthesis = m_typedChar != QLatin1Char('(');
252
253 if (completionSettings.m_spaceAfterFunctionName)
254 extraChars += QLatin1Char(' ');
255 extraChars += QLatin1Char('(');
256 if (m_typedChar == QLatin1Char('('))
257 m_typedChar = QChar();
258
259 // If the function doesn't return anything, automatically place the semicolon,
260 // unless we're doing a scope completion (then it might be function definition).
261 const QChar characterAtCursor = manipulator.characterAt(manipulator.currentPosition());
262 bool endWithSemicolon = m_typedChar == QLatin1Char(';')
263 || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON);
264 const QChar semicolon = m_typedChar.isNull() ? QLatin1Char(';') : m_typedChar;
265
266 if (endWithSemicolon && characterAtCursor == semicolon) {
267 endWithSemicolon = false;
268 m_typedChar = QChar();
269 }
270
271 // If the function takes no arguments, automatically place the closing parenthesis
272 if (!isOverloaded() && !function->hasArguments() && skipClosingParenthesis) {
273 extraChars += QLatin1Char(')');
274 if (endWithSemicolon) {
275 extraChars += semicolon;
276 m_typedChar = QChar();
277 }
278 } else if (autoParenthesesEnabled) {
279 const QChar lookAhead = manipulator.characterAt(manipulator.currentPosition() + 1);
280 if (MatchingText::shouldInsertMatchingText(lookAhead)) {
281 extraChars += QLatin1Char(')');
282 --cursorOffset;
283 setAutoCompleteSkipPos = true;
284 if (endWithSemicolon) {
285 extraChars += semicolon;
286 --cursorOffset;
287 m_typedChar = QChar();
288 }
289 }
290 // TODO: When an opening parenthesis exists, the "semicolon" should really be
291 // inserted after the matching closing parenthesis.
292 }
293 }
294 }
295 }
296
297 if (autoInsertBrackets && data().canConvert<CompleteFunctionDeclaration>()) {
298 if (m_typedChar == QLatin1Char('('))
299 m_typedChar = QChar();
300
301 // everything from the closing parenthesis on are extra chars, to
302 // make sure an auto-inserted ")" gets replaced by ") const" if necessary
303 int closingParen = toInsert.lastIndexOf(QLatin1Char(')'));
304 extraChars = toInsert.mid(closingParen);
305 toInsert.truncate(closingParen);
306 }
307 }
308
309 // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before
310 if (!m_typedChar.isNull()) {
311 extraChars += m_typedChar;
312 if (cursorOffset != 0)
313 --cursorOffset;
314 }
315
316 // Avoid inserting characters that are already there
317 int currentPosition = manipulator.currentPosition();
318 QTextCursor cursor = manipulator.textCursorAt(basePosition);
319 cursor.movePosition(QTextCursor::EndOfWord);
320 const QString textAfterCursor = manipulator.textAt(currentPosition,
321 cursor.position() - currentPosition);
322 if (toInsert != textAfterCursor
323 && toInsert.indexOf(textAfterCursor, currentPosition - basePosition) >= 0) {
324 currentPosition = cursor.position();
325 }
326
327 for (int i = 0; i < extraChars.length(); ++i) {
328 const QChar a = extraChars.at(i);
329 const QChar b = manipulator.characterAt(currentPosition + i);
330 if (a == b)
331 ++extraLength;
332 else
333 break;
334 }
335
336 toInsert += extraChars;
337
338 // Insert the remainder of the name
339 const int length = currentPosition - basePosition + extraLength;
340 manipulator.replace(basePosition, length, toInsert);
341 manipulator.setCursorPosition(basePosition + toInsert.length());
342 if (cursorOffset)
343 manipulator.setCursorPosition(manipulator.currentPosition() + cursorOffset);
344 if (setAutoCompleteSkipPos)
345 manipulator.setAutoCompleteSkipPosition(manipulator.currentPosition());
346 }
347
348 // --------------------
349 // CppFunctionHintModel
350 // --------------------
351 class CppFunctionHintModel : public IFunctionHintProposalModel
352 {
353 public:
CppFunctionHintModel(const QList<Function * > & functionSymbols,const QSharedPointer<TypeOfExpression> & typeOfExp)354 CppFunctionHintModel(const QList<Function *> &functionSymbols,
355 const QSharedPointer<TypeOfExpression> &typeOfExp)
356 : m_functionSymbols(functionSymbols)
357 , m_currentArg(-1)
358 , m_typeOfExpression(typeOfExp)
359 {}
360
reset()361 void reset() override {}
size() const362 int size() const override { return m_functionSymbols.size(); }
363 QString text(int index) const override;
364 int activeArgument(const QString &prefix) const override;
365
366 private:
367 QList<Function *> m_functionSymbols;
368 mutable int m_currentArg;
369 QSharedPointer<TypeOfExpression> m_typeOfExpression;
370 };
371
text(int index) const372 QString CppFunctionHintModel::text(int index) const
373 {
374 Overview overview;
375 overview.showReturnTypes = true;
376 overview.showArgumentNames = true;
377 overview.markedArgument = m_currentArg + 1;
378 Function *f = m_functionSymbols.at(index);
379
380 const QString prettyMethod = overview.prettyType(f->type(), f->name());
381 const int begin = overview.markedArgumentBegin;
382 const int end = overview.markedArgumentEnd;
383
384 QString hintText;
385 hintText += prettyMethod.left(begin).toHtmlEscaped();
386 hintText += QLatin1String("<b>");
387 hintText += prettyMethod.mid(begin, end - begin).toHtmlEscaped();
388 hintText += QLatin1String("</b>");
389 hintText += prettyMethod.mid(end).toHtmlEscaped();
390 return hintText;
391 }
392
activeArgument(const QString & prefix) const393 int CppFunctionHintModel::activeArgument(const QString &prefix) const
394 {
395 int argnr = 0;
396 int parcount = 0;
397 SimpleLexer tokenize;
398 Tokens tokens = tokenize(prefix);
399 for (int i = 0; i < tokens.count(); ++i) {
400 const Token &tk = tokens.at(i);
401 if (tk.is(T_LPAREN))
402 ++parcount;
403 else if (tk.is(T_RPAREN))
404 --parcount;
405 else if (!parcount && tk.is(T_COMMA))
406 ++argnr;
407 }
408
409 if (parcount < 0)
410 return -1;
411
412 if (argnr != m_currentArg)
413 m_currentArg = argnr;
414
415 return argnr;
416 }
417
418 // ---------------------------
419 // InternalCompletionAssistProvider
420 // ---------------------------
421
createProcessor() const422 IAssistProcessor *InternalCompletionAssistProvider::createProcessor() const
423 {
424 return new InternalCppCompletionAssistProcessor;
425 }
426
createAssistInterface(const Utils::FilePath & filePath,const TextEditorWidget * textEditorWidget,const LanguageFeatures & languageFeatures,int position,AssistReason reason) const427 AssistInterface *InternalCompletionAssistProvider::createAssistInterface(
428 const Utils::FilePath &filePath,
429 const TextEditorWidget *textEditorWidget,
430 const LanguageFeatures &languageFeatures,
431 int position,
432 AssistReason reason) const
433 {
434 QTC_ASSERT(textEditorWidget, return nullptr);
435
436 return new CppCompletionAssistInterface(filePath,
437 textEditorWidget,
438 BuiltinEditorDocumentParser::get(filePath.toString()),
439 languageFeatures,
440 position,
441 reason,
442 CppModelManager::instance()->workingCopy());
443 }
444
445 // -----------------
446 // CppAssistProposal
447 // -----------------
448 class CppAssistProposal : public GenericProposal
449 {
450 public:
CppAssistProposal(int cursorPos,GenericProposalModelPtr model)451 CppAssistProposal(int cursorPos, GenericProposalModelPtr model)
452 : GenericProposal(cursorPos, model)
453 , m_replaceDotForArrow(model.staticCast<CppAssistProposalModel>()->m_replaceDotForArrow)
454 {}
455
isCorrective(TextEditorWidget *) const456 bool isCorrective(TextEditorWidget *) const override { return m_replaceDotForArrow; }
457 void makeCorrection(TextEditorWidget *editorWidget) override;
458
459 private:
460 bool m_replaceDotForArrow;
461 };
462
makeCorrection(TextEditorWidget * editorWidget)463 void CppAssistProposal::makeCorrection(TextEditorWidget *editorWidget)
464 {
465 const int oldPosition = editorWidget->position();
466 editorWidget->setCursorPosition(basePosition() - 1);
467 editorWidget->replace(1, QLatin1String("->"));
468 editorWidget->setCursorPosition(oldPosition + 1);
469 moveBasePosition(1);
470 }
471
472 namespace {
473
474 class ConvertToCompletionItem: protected NameVisitor
475 {
476 // The completion item.
477 AssistProposalItem *_item = nullptr;
478
479 // The current symbol.
480 Symbol *_symbol = nullptr;
481
482 // The pretty printer.
483 Overview overview;
484
485 public:
ConvertToCompletionItem()486 ConvertToCompletionItem()
487 {
488 overview.showReturnTypes = true;
489 overview.showArgumentNames = true;
490 }
491
operator ()(Symbol * symbol)492 AssistProposalItem *operator()(Symbol *symbol)
493 {
494 //using declaration can be qualified
495 if (!symbol || !symbol->name() || (symbol->name()->isQualifiedNameId()
496 && !symbol->asUsingDeclaration()))
497 return nullptr;
498
499 AssistProposalItem *previousItem = switchCompletionItem(nullptr);
500 Symbol *previousSymbol = switchSymbol(symbol);
501 accept(symbol->unqualifiedName());
502 if (_item)
503 _item->setData(QVariant::fromValue(symbol));
504 (void) switchSymbol(previousSymbol);
505 return switchCompletionItem(previousItem);
506 }
507
508 protected:
switchSymbol(Symbol * symbol)509 Symbol *switchSymbol(Symbol *symbol)
510 {
511 Symbol *previousSymbol = _symbol;
512 _symbol = symbol;
513 return previousSymbol;
514 }
515
switchCompletionItem(AssistProposalItem * item)516 AssistProposalItem *switchCompletionItem(AssistProposalItem *item)
517 {
518 AssistProposalItem *previousItem = _item;
519 _item = item;
520 return previousItem;
521 }
522
newCompletionItem(const Name * name)523 AssistProposalItem *newCompletionItem(const Name *name)
524 {
525 AssistProposalItem *item = new CppAssistProposalItem;
526 item->setText(overview.prettyName(name));
527 return item;
528 }
529
visit(const Identifier * name)530 void visit(const Identifier *name) override
531 {
532 _item = newCompletionItem(name);
533 if (!_symbol->isScope() || _symbol->isFunction())
534 _item->setDetail(overview.prettyType(_symbol->type(), name));
535 }
536
visit(const TemplateNameId * name)537 void visit(const TemplateNameId *name) override
538 {
539 _item = newCompletionItem(name);
540 _item->setText(QString::fromUtf8(name->identifier()->chars(), name->identifier()->size()));
541 }
542
visit(const DestructorNameId * name)543 void visit(const DestructorNameId *name) override
544 { _item = newCompletionItem(name); }
545
visit(const OperatorNameId * name)546 void visit(const OperatorNameId *name) override
547 {
548 _item = newCompletionItem(name);
549 _item->setDetail(overview.prettyType(_symbol->type(), name));
550 }
551
visit(const ConversionNameId * name)552 void visit(const ConversionNameId *name) override
553 { _item = newCompletionItem(name); }
554
visit(const QualifiedNameId * name)555 void visit(const QualifiedNameId *name) override
556 { _item = newCompletionItem(name->name()); }
557 };
558
asClassOrTemplateClassType(FullySpecifiedType ty)559 Class *asClassOrTemplateClassType(FullySpecifiedType ty)
560 {
561 if (Class *classTy = ty->asClassType())
562 return classTy;
563 if (Template *templ = ty->asTemplateType()) {
564 if (Symbol *decl = templ->declaration())
565 return decl->asClass();
566 }
567 return nullptr;
568 }
569
enclosingNonTemplateScope(Symbol * symbol)570 Scope *enclosingNonTemplateScope(Symbol *symbol)
571 {
572 if (symbol) {
573 if (Scope *scope = symbol->enclosingScope()) {
574 if (Template *templ = scope->asTemplate())
575 return templ->enclosingScope();
576 return scope;
577 }
578 }
579 return nullptr;
580 }
581
asFunctionOrTemplateFunctionType(FullySpecifiedType ty)582 Function *asFunctionOrTemplateFunctionType(FullySpecifiedType ty)
583 {
584 if (Function *funTy = ty->asFunctionType())
585 return funTy;
586 if (Template *templ = ty->asTemplateType()) {
587 if (Symbol *decl = templ->declaration())
588 return decl->asFunction();
589 }
590 return nullptr;
591 }
592
isQPrivateSignal(const Symbol * symbol)593 bool isQPrivateSignal(const Symbol *symbol)
594 {
595 if (!symbol)
596 return false;
597
598 static Identifier qPrivateSignalIdentifier("QPrivateSignal", 14);
599
600 if (FullySpecifiedType type = symbol->type()) {
601 if (NamedType *namedType = type->asNamedType()) {
602 if (const Name *name = namedType->name()) {
603 if (name->match(&qPrivateSignalIdentifier))
604 return true;
605 }
606 }
607 }
608 return false;
609 }
610
createQt4SignalOrSlot(CPlusPlus::Function * function,const Overview & overview)611 QString createQt4SignalOrSlot(CPlusPlus::Function *function, const Overview &overview)
612 {
613 QString signature;
614 signature += Overview().prettyName(function->name());
615 signature += QLatin1Char('(');
616 for (unsigned i = 0, to = function->argumentCount(); i < to; ++i) {
617 Symbol *arg = function->argumentAt(i);
618 if (isQPrivateSignal(arg))
619 continue;
620 if (i != 0)
621 signature += QLatin1Char(',');
622 signature += overview.prettyType(arg->type());
623 }
624 signature += QLatin1Char(')');
625
626 const QByteArray normalized = QMetaObject::normalizedSignature(signature.toUtf8());
627 return QString::fromUtf8(normalized, normalized.size());
628 }
629
createQt5SignalOrSlot(CPlusPlus::Function * function,const Overview & overview)630 QString createQt5SignalOrSlot(CPlusPlus::Function *function, const Overview &overview)
631 {
632 QString text;
633 text += overview.prettyName(function->name());
634 return text;
635 }
636
637 /*!
638 \class BackwardsEater
639 \brief Checks strings and expressions before given position.
640
641 Similar to BackwardsScanner, but also can handle expressions. Ignores whitespace.
642 */
643 class BackwardsEater
644 {
645 public:
BackwardsEater(const CppCompletionAssistInterface * assistInterface,int position)646 explicit BackwardsEater(const CppCompletionAssistInterface *assistInterface, int position)
647 : m_position(position)
648 , m_assistInterface(assistInterface)
649 {
650 }
651
isPositionValid() const652 bool isPositionValid() const
653 {
654 return m_position >= 0;
655 }
656
eatConnectOpenParenthesis()657 bool eatConnectOpenParenthesis()
658 {
659 return eatString(QLatin1String("(")) && eatString(QLatin1String("connect"));
660 }
661
eatExpressionCommaAmpersand()662 bool eatExpressionCommaAmpersand()
663 {
664 return eatString(QLatin1String("&")) && eatString(QLatin1String(",")) && eatExpression();
665 }
666
eatConnectOpenParenthesisExpressionCommaAmpersandExpressionComma()667 bool eatConnectOpenParenthesisExpressionCommaAmpersandExpressionComma()
668 {
669 return eatString(QLatin1String(","))
670 && eatExpression()
671 && eatExpressionCommaAmpersand()
672 && eatConnectOpenParenthesis();
673 }
674
675 private:
eatExpression()676 bool eatExpression()
677 {
678 if (!isPositionValid())
679 return false;
680
681 maybeEatWhitespace();
682
683 QTextCursor cursor(m_assistInterface->textDocument());
684 cursor.setPosition(m_position + 1);
685 ExpressionUnderCursor expressionUnderCursor(m_assistInterface->languageFeatures());
686 const QString expression = expressionUnderCursor(cursor);
687 if (expression.isEmpty())
688 return false;
689 m_position = m_position - expression.length();
690 return true;
691 }
692
eatString(const QString & string)693 bool eatString(const QString &string)
694 {
695 if (!isPositionValid())
696 return false;
697
698 if (string.isEmpty())
699 return true;
700
701 maybeEatWhitespace();
702
703 const int stringLength = string.length();
704 const int stringStart = m_position - (stringLength - 1);
705
706 if (stringStart < 0)
707 return false;
708
709 if (m_assistInterface->textAt(stringStart, stringLength) == string) {
710 m_position = stringStart - 1;
711 return true;
712 }
713
714 return false;
715 }
716
maybeEatWhitespace()717 void maybeEatWhitespace()
718 {
719 while (isPositionValid() && m_assistInterface->characterAt(m_position).isSpace())
720 --m_position;
721 }
722
723 private:
724 int m_position;
725 const CppCompletionAssistInterface * const m_assistInterface;
726 };
727
canCompleteConnectSignalAt2ndArgument(const CppCompletionAssistInterface * assistInterface,int startOfExpression)728 bool canCompleteConnectSignalAt2ndArgument(const CppCompletionAssistInterface *assistInterface,
729 int startOfExpression)
730 {
731 BackwardsEater eater(assistInterface, startOfExpression);
732
733 return eater.isPositionValid()
734 && eater.eatExpressionCommaAmpersand()
735 && eater.eatConnectOpenParenthesis();
736 }
737
canCompleteConnectSignalAt4thArgument(const CppCompletionAssistInterface * assistInterface,int startPosition)738 bool canCompleteConnectSignalAt4thArgument(const CppCompletionAssistInterface *assistInterface,
739 int startPosition)
740 {
741 BackwardsEater eater(assistInterface, startPosition);
742
743 return eater.isPositionValid()
744 && eater.eatExpressionCommaAmpersand()
745 && eater.eatConnectOpenParenthesisExpressionCommaAmpersandExpressionComma();
746 }
747
canCompleteClassNameAt2ndOr4thConnectArgument(const CppCompletionAssistInterface * assistInterface,int startPosition)748 bool canCompleteClassNameAt2ndOr4thConnectArgument(
749 const CppCompletionAssistInterface *assistInterface,
750 int startPosition)
751 {
752 BackwardsEater eater(assistInterface, startPosition);
753
754 if (!eater.isPositionValid())
755 return false;
756
757 return eater.eatConnectOpenParenthesis()
758 || eater.eatConnectOpenParenthesisExpressionCommaAmpersandExpressionComma();
759 }
760
classOrNamespaceFromLookupItem(const LookupItem & lookupItem,const LookupContext & context)761 ClassOrNamespace *classOrNamespaceFromLookupItem(const LookupItem &lookupItem,
762 const LookupContext &context)
763 {
764 const Name *name = nullptr;
765
766 if (Symbol *d = lookupItem.declaration()) {
767 if (Class *k = d->asClass())
768 name = k->name();
769 }
770
771 if (!name) {
772 FullySpecifiedType type = lookupItem.type().simplified();
773
774 if (PointerType *pointerType = type->asPointerType())
775 type = pointerType->elementType().simplified();
776 else
777 return nullptr; // not a pointer or a reference to a pointer.
778
779 NamedType *namedType = type->asNamedType();
780 if (!namedType) // not a class name.
781 return nullptr;
782
783 name = namedType->name();
784 }
785
786 return name ? context.lookupType(name, lookupItem.scope()) : nullptr;
787 }
788
classFromLookupItem(const LookupItem & lookupItem,const LookupContext & context)789 Class *classFromLookupItem(const LookupItem &lookupItem, const LookupContext &context)
790 {
791 ClassOrNamespace *b = classOrNamespaceFromLookupItem(lookupItem, context);
792 if (!b)
793 return nullptr;
794
795 foreach (Symbol *s, b->symbols()) {
796 if (Class *klass = s->asClass())
797 return klass;
798 }
799 return nullptr;
800 }
801
minimalName(Symbol * symbol,Scope * targetScope,const LookupContext & context)802 const Name *minimalName(Symbol *symbol, Scope *targetScope, const LookupContext &context)
803 {
804 ClassOrNamespace *target = context.lookupType(targetScope);
805 if (!target)
806 target = context.globalNamespace();
807 return LookupContext::minimalName(symbol, target, context.bindings()->control().data());
808 }
809
810 } // Anonymous
811
812 // ------------------------------------
813 // InternalCppCompletionAssistProcessor
814 // ------------------------------------
InternalCppCompletionAssistProcessor()815 InternalCppCompletionAssistProcessor::InternalCppCompletionAssistProcessor()
816 : m_model(new CppAssistProposalModel)
817 {
818 }
819
820 InternalCppCompletionAssistProcessor::~InternalCppCompletionAssistProcessor() = default;
821
perform(const AssistInterface * interface)822 IAssistProposal * InternalCppCompletionAssistProcessor::perform(const AssistInterface *interface)
823 {
824 m_interface.reset(static_cast<const CppCompletionAssistInterface *>(interface));
825
826 if (interface->reason() != ExplicitlyInvoked && !accepts())
827 return nullptr;
828
829 int index = startCompletionHelper();
830 if (index != -1) {
831 if (m_hintProposal)
832 return m_hintProposal;
833
834 return createContentProposal();
835 }
836
837 return nullptr;
838 }
839
accepts() const840 bool InternalCppCompletionAssistProcessor::accepts() const
841 {
842 const int pos = m_interface->position();
843 unsigned token = T_EOF_SYMBOL;
844
845 const int start = startOfOperator(pos, &token, /*want function call=*/ true);
846 if (start != pos) {
847 if (token == T_POUND) {
848 const int column = pos - m_interface->textDocument()->findBlock(start).position();
849 if (column != 1)
850 return false;
851 }
852
853 return true;
854 } else {
855 // Trigger completion after n characters of a name have been typed, when not editing an existing name
856 QChar characterUnderCursor = m_interface->characterAt(pos);
857
858 if (!isValidIdentifierChar(characterUnderCursor)) {
859 const int startOfName = findStartOfName(pos);
860 if (pos - startOfName >= TextEditorSettings::completionSettings().m_characterThreshold) {
861 const QChar firstCharacter = m_interface->characterAt(startOfName);
862 if (isValidFirstIdentifierChar(firstCharacter)) {
863 // Finally check that we're not inside a comment or string (code copied from startOfOperator)
864 QTextCursor tc(m_interface->textDocument());
865 tc.setPosition(pos);
866
867 SimpleLexer tokenize;
868 tokenize.setLanguageFeatures(m_interface->languageFeatures());
869 tokenize.setSkipComments(false);
870
871 const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
872 const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1));
873 const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
874
875 if (!tk.isComment() && !tk.isLiteral()) {
876 return true;
877 } else if (tk.isLiteral()
878 && tokens.size() == 3
879 && tokens.at(0).kind() == T_POUND
880 && tokens.at(1).kind() == T_IDENTIFIER) {
881 const QString &line = tc.block().text();
882 const Token &idToken = tokens.at(1);
883 QStringView identifier = idToken.utf16charsEnd() > line.size()
884 ? QStringView(line).mid(
885 idToken.utf16charsBegin())
886 : QStringView(line)
887 .mid(idToken.utf16charsBegin(),
888 idToken.utf16chars());
889 if (identifier == QLatin1String("include")
890 || identifier == QLatin1String("include_next")
891 || (m_interface->languageFeatures().objCEnabled && identifier == QLatin1String("import"))) {
892 return true;
893 }
894 }
895 }
896 }
897 }
898 }
899
900 return false;
901 }
902
createContentProposal()903 IAssistProposal *InternalCppCompletionAssistProcessor::createContentProposal()
904 {
905 // Duplicates are kept only if they are snippets.
906 QSet<QString> processed;
907 auto it = m_completions.begin();
908 while (it != m_completions.end()) {
909 auto item = static_cast<CppAssistProposalItem *>(*it);
910 if (!processed.contains(item->text()) || item->isSnippet()) {
911 ++it;
912 if (!item->isSnippet()) {
913 processed.insert(item->text());
914 if (!item->isOverloaded()) {
915 if (auto symbol = qvariant_cast<Symbol *>(item->data())) {
916 if (Function *funTy = symbol->type()->asFunctionType()) {
917 if (funTy->hasArguments())
918 item->markAsOverloaded();
919 }
920 }
921 }
922 }
923 } else {
924 delete *it;
925 it = m_completions.erase(it);
926 }
927 }
928
929 m_model->loadContent(m_completions);
930 return new CppAssistProposal(m_positionForProposal, m_model);
931 }
932
createHintProposal(QList<Function * > functionSymbols) const933 IAssistProposal *InternalCppCompletionAssistProcessor::createHintProposal(
934 QList<Function *> functionSymbols) const
935 {
936 FunctionHintProposalModelPtr model(new CppFunctionHintModel(functionSymbols,
937 m_model->m_typeOfExpression));
938 return new FunctionHintProposal(m_positionForProposal, model);
939 }
940
startOfOperator(int positionInDocument,unsigned * kind,bool wantFunctionCall) const941 int InternalCppCompletionAssistProcessor::startOfOperator(int positionInDocument,
942 unsigned *kind,
943 bool wantFunctionCall) const
944 {
945 const QChar ch = m_interface->characterAt(positionInDocument - 1);
946 const QChar ch2 = m_interface->characterAt(positionInDocument - 2);
947 const QChar ch3 = m_interface->characterAt(positionInDocument - 3);
948
949 int start = positionInDocument
950 - CppCompletionAssistProvider::activationSequenceChar(ch, ch2, ch3, kind,
951 wantFunctionCall,
952 /*wantQt5SignalSlots*/ true);
953
954 const auto dotAtIncludeCompletionHandler = [this](int &start, unsigned *kind) {
955 start = findStartOfName(start);
956 const QChar ch4 = m_interface->characterAt(start - 1);
957 const QChar ch5 = m_interface->characterAt(start - 2);
958 const QChar ch6 = m_interface->characterAt(start - 3);
959 start = start - CppCompletionAssistProvider::activationSequenceChar(
960 ch4, ch5, ch6, kind, false, false);
961 };
962
963 CppCompletionAssistProcessor::startOfOperator(m_interface->textDocument(),
964 positionInDocument,
965 kind,
966 start,
967 m_interface->languageFeatures(),
968 /*adjustForQt5SignalSlotCompletion=*/ true,
969 dotAtIncludeCompletionHandler);
970 return start;
971 }
972
findStartOfName(int pos) const973 int InternalCppCompletionAssistProcessor::findStartOfName(int pos) const
974 {
975 if (pos == -1)
976 pos = m_interface->position();
977 QChar chr;
978
979 // Skip to the start of a name
980 do {
981 chr = m_interface->characterAt(--pos);
982 } while (CppTools::isValidIdentifierChar(chr));
983
984 return pos + 1;
985 }
986
startCompletionHelper()987 int InternalCppCompletionAssistProcessor::startCompletionHelper()
988 {
989 if (m_interface->languageFeatures().objCEnabled) {
990 if (tryObjCCompletion())
991 return m_positionForProposal;
992 }
993
994 const int startOfName = findStartOfName();
995 m_positionForProposal = startOfName;
996 m_model->m_completionOperator = T_EOF_SYMBOL;
997
998 int endOfOperator = m_positionForProposal;
999
1000 // Skip whitespace preceding this position
1001 while (m_interface->characterAt(endOfOperator - 1).isSpace())
1002 --endOfOperator;
1003
1004 int endOfExpression = startOfOperator(endOfOperator,
1005 &m_model->m_completionOperator,
1006 /*want function call =*/ true);
1007
1008 if (m_model->m_completionOperator == T_DOXY_COMMENT) {
1009 for (int i = 1; i < T_DOXY_LAST_TAG; ++i)
1010 addCompletionItem(QString::fromLatin1(doxygenTagSpell(i)), Icons::keywordIcon());
1011 return m_positionForProposal;
1012 }
1013
1014 // Pre-processor completion
1015 if (m_model->m_completionOperator == T_POUND) {
1016 completePreprocessor();
1017 m_positionForProposal = startOfName;
1018 return m_positionForProposal;
1019 }
1020
1021 // Include completion
1022 if (m_model->m_completionOperator == T_STRING_LITERAL
1023 || m_model->m_completionOperator == T_ANGLE_STRING_LITERAL
1024 || m_model->m_completionOperator == T_SLASH) {
1025
1026 QTextCursor c(m_interface->textDocument());
1027 c.setPosition(endOfExpression);
1028 if (completeInclude(c))
1029 m_positionForProposal = endOfExpression + 1;
1030 return m_positionForProposal;
1031 }
1032
1033 ExpressionUnderCursor expressionUnderCursor(m_interface->languageFeatures());
1034 QTextCursor tc(m_interface->textDocument());
1035
1036 if (m_model->m_completionOperator == T_COMMA) {
1037 tc.setPosition(endOfExpression);
1038 const int start = expressionUnderCursor.startOfFunctionCall(tc);
1039 if (start == -1) {
1040 m_model->m_completionOperator = T_EOF_SYMBOL;
1041 return -1;
1042 }
1043
1044 endOfExpression = start;
1045 m_positionForProposal = start + 1;
1046 m_model->m_completionOperator = T_LPAREN;
1047 }
1048
1049 QString expression;
1050 int startOfExpression = m_interface->position();
1051 tc.setPosition(endOfExpression);
1052
1053 if (m_model->m_completionOperator) {
1054 expression = expressionUnderCursor(tc);
1055 startOfExpression = endOfExpression - expression.length();
1056
1057 if (m_model->m_completionOperator == T_AMPER) {
1058 // We expect 'expression' to be either "sender" or "receiver" in
1059 // "connect(sender, &" or
1060 // "connect(otherSender, &Foo::signal1, receiver, &"
1061 const int beforeExpression = startOfExpression - 1;
1062 if (canCompleteClassNameAt2ndOr4thConnectArgument(m_interface.data(),
1063 beforeExpression)) {
1064 m_model->m_completionOperator = CompleteQt5SignalOrSlotClassNameTrigger;
1065 } else { // Ensure global completion
1066 startOfExpression = endOfExpression = m_positionForProposal;
1067 expression.clear();
1068 m_model->m_completionOperator = T_EOF_SYMBOL;
1069 }
1070 } else if (m_model->m_completionOperator == T_COLON_COLON) {
1071 // We expect 'expression' to be "Foo" in
1072 // "connect(sender, &Foo::" or
1073 // "connect(sender, &Bar::signal1, receiver, &Foo::"
1074 const int beforeExpression = startOfExpression - 1;
1075 if (canCompleteConnectSignalAt2ndArgument(m_interface.data(), beforeExpression))
1076 m_model->m_completionOperator = CompleteQt5SignalTrigger;
1077 else if (canCompleteConnectSignalAt4thArgument(m_interface.data(), beforeExpression))
1078 m_model->m_completionOperator = CompleteQt5SlotTrigger;
1079 } else if (m_model->m_completionOperator == T_LPAREN) {
1080 if (expression.endsWith(QLatin1String("SIGNAL"))) {
1081 m_model->m_completionOperator = T_SIGNAL;
1082 } else if (expression.endsWith(QLatin1String("SLOT"))) {
1083 m_model->m_completionOperator = T_SLOT;
1084 } else if (m_interface->position() != endOfOperator) {
1085 // We don't want a function completion when the cursor isn't at the opening brace
1086 expression.clear();
1087 m_model->m_completionOperator = T_EOF_SYMBOL;
1088 m_positionForProposal = startOfName;
1089 startOfExpression = m_interface->position();
1090 }
1091 }
1092 } else if (expression.isEmpty()) {
1093 while (startOfExpression > 0 && m_interface->characterAt(startOfExpression).isSpace())
1094 --startOfExpression;
1095 }
1096
1097 int line = 0, column = 0;
1098 Utils::Text::convertPosition(m_interface->textDocument(), startOfExpression, &line, &column);
1099 const QString fileName = m_interface->filePath().toString();
1100 return startCompletionInternal(fileName, line, column - 1, expression, endOfExpression);
1101 }
1102
tryObjCCompletion()1103 bool InternalCppCompletionAssistProcessor::tryObjCCompletion()
1104 {
1105 int end = m_interface->position();
1106 while (m_interface->characterAt(end).isSpace())
1107 ++end;
1108 if (m_interface->characterAt(end) != QLatin1Char(']'))
1109 return false;
1110
1111 QTextCursor tc(m_interface->textDocument());
1112 tc.setPosition(end);
1113 BackwardsScanner tokens(tc, m_interface->languageFeatures());
1114 if (tokens[tokens.startToken() - 1].isNot(T_RBRACKET))
1115 return false;
1116
1117 const int start = tokens.startOfMatchingBrace(tokens.startToken());
1118 if (start == tokens.startToken())
1119 return false;
1120
1121 const int startPos = tokens[start].bytesBegin() + tokens.startPosition();
1122 const QString expr = m_interface->textAt(startPos, m_interface->position() - startPos);
1123
1124 Document::Ptr thisDocument = m_interface->snapshot().document(m_interface->filePath());
1125 if (!thisDocument)
1126 return false;
1127
1128 m_model->m_typeOfExpression->init(thisDocument, m_interface->snapshot());
1129
1130 int line = 0, column = 0;
1131 Utils::Text::convertPosition(m_interface->textDocument(), m_interface->position(), &line,
1132 &column);
1133 Scope *scope = thisDocument->scopeAt(line, column - 1);
1134 if (!scope)
1135 return false;
1136
1137 const QList<LookupItem> items = (*m_model->m_typeOfExpression)(expr.toUtf8(), scope);
1138 LookupContext lookupContext(thisDocument, m_interface->snapshot());
1139
1140 foreach (const LookupItem &item, items) {
1141 FullySpecifiedType ty = item.type().simplified();
1142 if (ty->isPointerType()) {
1143 ty = ty->asPointerType()->elementType().simplified();
1144
1145 if (NamedType *namedTy = ty->asNamedType()) {
1146 ClassOrNamespace *binding = lookupContext.lookupType(namedTy->name(), item.scope());
1147 completeObjCMsgSend(binding, false);
1148 }
1149 } else {
1150 if (ObjCClass *clazz = ty->asObjCClassType()) {
1151 ClassOrNamespace *binding = lookupContext.lookupType(clazz->name(), item.scope());
1152 completeObjCMsgSend(binding, true);
1153 }
1154 }
1155 }
1156
1157 if (m_completions.isEmpty())
1158 return false;
1159
1160 m_positionForProposal = m_interface->position();
1161 return true;
1162 }
1163
1164 namespace {
1165 enum CompletionOrder {
1166 // default order is 0
1167 FunctionArgumentsOrder = 2,
1168 FunctionLocalsOrder = 2, // includes local types
1169 PublicClassMemberOrder = 1,
1170 InjectedClassNameOrder = -1,
1171 MacrosOrder = -2,
1172 KeywordsOrder = -2
1173 };
1174 }
1175
addCompletionItem(const QString & text,const QIcon & icon,int order,const QVariant & data)1176 void InternalCppCompletionAssistProcessor::addCompletionItem(const QString &text,
1177 const QIcon &icon,
1178 int order,
1179 const QVariant &data)
1180 {
1181 AssistProposalItem *item = new CppAssistProposalItem;
1182 item->setText(text);
1183 item->setIcon(icon);
1184 item->setOrder(order);
1185 item->setData(data);
1186 m_completions.append(item);
1187 }
1188
addCompletionItem(Symbol * symbol,int order)1189 void InternalCppCompletionAssistProcessor::addCompletionItem(Symbol *symbol, int order)
1190 {
1191 ConvertToCompletionItem toCompletionItem;
1192 AssistProposalItem *item = toCompletionItem(symbol);
1193 if (item) {
1194 item->setIcon(Icons::iconForSymbol(symbol));
1195 item->setOrder(order);
1196 m_completions.append(item);
1197 }
1198 }
1199
completeObjCMsgSend(ClassOrNamespace * binding,bool staticClassAccess)1200 void InternalCppCompletionAssistProcessor::completeObjCMsgSend(ClassOrNamespace *binding,
1201 bool staticClassAccess)
1202 {
1203 QList<Scope*> memberScopes;
1204 foreach (Symbol *s, binding->symbols()) {
1205 if (ObjCClass *c = s->asObjCClass())
1206 memberScopes.append(c);
1207 }
1208
1209 foreach (Scope *scope, memberScopes) {
1210 for (int i = 0; i < scope->memberCount(); ++i) {
1211 Symbol *symbol = scope->memberAt(i);
1212
1213 if (ObjCMethod *method = symbol->type()->asObjCMethodType()) {
1214 if (method->isStatic() == staticClassAccess) {
1215 Overview oo;
1216 const SelectorNameId *selectorName =
1217 method->name()->asSelectorNameId();
1218 QString text;
1219 QString data;
1220 if (selectorName->hasArguments()) {
1221 for (int i = 0; i < selectorName->nameCount(); ++i) {
1222 if (i > 0)
1223 text += QLatin1Char(' ');
1224 Symbol *arg = method->argumentAt(i);
1225 text += QString::fromUtf8(selectorName->nameAt(i)->identifier()->chars());
1226 text += QLatin1Char(':');
1227 text += Snippet::kVariableDelimiter;
1228 text += QLatin1Char('(');
1229 text += oo.prettyType(arg->type());
1230 text += QLatin1Char(')');
1231 text += oo.prettyName(arg->name());
1232 text += Snippet::kVariableDelimiter;
1233 }
1234 } else {
1235 text = QString::fromUtf8(selectorName->identifier()->chars());
1236 }
1237 data = text;
1238
1239 if (!text.isEmpty())
1240 addCompletionItem(text, QIcon(), 0, QVariant::fromValue(data));
1241 }
1242 }
1243 }
1244 }
1245 }
1246
completeInclude(const QTextCursor & cursor)1247 bool InternalCppCompletionAssistProcessor::completeInclude(const QTextCursor &cursor)
1248 {
1249 QString directoryPrefix;
1250 if (m_model->m_completionOperator == T_SLASH) {
1251 QTextCursor c = cursor;
1252 c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
1253 QString sel = c.selectedText();
1254 int startCharPos = sel.indexOf(QLatin1Char('"'));
1255 if (startCharPos == -1) {
1256 startCharPos = sel.indexOf(QLatin1Char('<'));
1257 m_model->m_completionOperator = T_ANGLE_STRING_LITERAL;
1258 } else {
1259 m_model->m_completionOperator = T_STRING_LITERAL;
1260 }
1261 if (startCharPos != -1)
1262 directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1);
1263 }
1264
1265 // Make completion for all relevant includes
1266 ProjectExplorer::HeaderPaths headerPaths = m_interface->headerPaths();
1267 const ProjectExplorer::HeaderPath currentFilePath(m_interface->filePath().toFileInfo().path(),
1268 ProjectExplorer::HeaderPathType::User);
1269 if (!headerPaths.contains(currentFilePath))
1270 headerPaths.append(currentFilePath);
1271
1272 const QStringList suffixes = Utils::mimeTypeForName(QLatin1String("text/x-c++hdr")).suffixes();
1273
1274 foreach (const ProjectExplorer::HeaderPath &headerPath, headerPaths) {
1275 QString realPath = headerPath.path;
1276 if (!directoryPrefix.isEmpty()) {
1277 realPath += QLatin1Char('/');
1278 realPath += directoryPrefix;
1279 if (headerPath.type == ProjectExplorer::HeaderPathType::Framework)
1280 realPath += QLatin1String(".framework/Headers");
1281 }
1282 completeInclude(realPath, suffixes);
1283 }
1284
1285 return !m_completions.isEmpty();
1286 }
1287
completeInclude(const QString & realPath,const QStringList & suffixes)1288 void InternalCppCompletionAssistProcessor::completeInclude(const QString &realPath,
1289 const QStringList &suffixes)
1290 {
1291 QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
1292 while (i.hasNext()) {
1293 const QString fileName = i.next();
1294 const QFileInfo fileInfo = i.fileInfo();
1295 const QString suffix = fileInfo.suffix();
1296 if (suffix.isEmpty() || suffixes.contains(suffix)) {
1297 QString text = fileName.mid(realPath.length() + 1);
1298 if (fileInfo.isDir())
1299 text += QLatin1Char('/');
1300 addCompletionItem(text, Icons::keywordIcon());
1301 }
1302 }
1303 }
1304
completePreprocessor()1305 void InternalCppCompletionAssistProcessor::completePreprocessor()
1306 {
1307 foreach (const QString &preprocessorCompletion, m_preprocessorCompletions)
1308 addCompletionItem(preprocessorCompletion);
1309
1310 if (objcKeywordsWanted())
1311 addCompletionItem(QLatin1String("import"));
1312 }
1313
objcKeywordsWanted() const1314 bool InternalCppCompletionAssistProcessor::objcKeywordsWanted() const
1315 {
1316 if (!m_interface->languageFeatures().objCEnabled)
1317 return false;
1318
1319 const Utils::MimeType mt = Utils::mimeTypeForFile(m_interface->filePath());
1320 return mt.matchesName(QLatin1String(CppTools::Constants::OBJECTIVE_C_SOURCE_MIMETYPE))
1321 || mt.matchesName(QLatin1String(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE));
1322 }
1323
startCompletionInternal(const QString & fileName,int line,int positionInBlock,const QString & expr,int endOfExpression)1324 int InternalCppCompletionAssistProcessor::startCompletionInternal(const QString &fileName,
1325 int line,
1326 int positionInBlock,
1327 const QString &expr,
1328 int endOfExpression)
1329 {
1330 QString expression = expr.trimmed();
1331
1332 Document::Ptr thisDocument = m_interface->snapshot().document(fileName);
1333 if (!thisDocument)
1334 return -1;
1335
1336 m_model->m_typeOfExpression->init(thisDocument, m_interface->snapshot());
1337
1338 Scope *scope = thisDocument->scopeAt(line, positionInBlock);
1339 QTC_ASSERT(scope, return -1);
1340
1341 if (expression.isEmpty()) {
1342 if (m_model->m_completionOperator == T_EOF_SYMBOL || m_model->m_completionOperator == T_COLON_COLON) {
1343 (void) (*m_model->m_typeOfExpression)(expression.toUtf8(), scope);
1344 return globalCompletion(scope) ? m_positionForProposal : -1;
1345 }
1346
1347 if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) {
1348 // Apply signal/slot completion on 'this'
1349 expression = QLatin1String("this");
1350 }
1351 }
1352
1353 QByteArray utf8Exp = expression.toUtf8();
1354 QList<LookupItem> results =
1355 (*m_model->m_typeOfExpression)(utf8Exp, scope, TypeOfExpression::Preprocess);
1356
1357 if (results.isEmpty()) {
1358 if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) {
1359 if (!(expression.isEmpty() || expression == QLatin1String("this"))) {
1360 expression = QLatin1String("this");
1361 results = (*m_model->m_typeOfExpression)(utf8Exp, scope);
1362 }
1363
1364 if (results.isEmpty())
1365 return -1;
1366
1367 } else if (m_model->m_completionOperator == T_LPAREN) {
1368 // Find the expression that precedes the current name
1369 int index = endOfExpression;
1370 while (m_interface->characterAt(index - 1).isSpace())
1371 --index;
1372 index = findStartOfName(index);
1373
1374 QTextCursor tc(m_interface->textDocument());
1375 tc.setPosition(index);
1376
1377 ExpressionUnderCursor expressionUnderCursor(m_interface->languageFeatures());
1378 const QString baseExpression = expressionUnderCursor(tc);
1379
1380 // Resolve the type of this expression
1381 const QList<LookupItem> results =
1382 (*m_model->m_typeOfExpression)(baseExpression.toUtf8(), scope,
1383 TypeOfExpression::Preprocess);
1384
1385 // If it's a class, add completions for the constructors
1386 foreach (const LookupItem &result, results) {
1387 if (result.type()->isClassType()) {
1388 if (completeConstructorOrFunction(results, endOfExpression, true))
1389 return m_positionForProposal;
1390
1391 break;
1392 }
1393 }
1394 return -1;
1395
1396 } else if (m_model->m_completionOperator == CompleteQt5SignalOrSlotClassNameTrigger) {
1397 // Fallback to global completion if we could not lookup sender/receiver object.
1398 return globalCompletion(scope) ? m_positionForProposal : -1;
1399
1400 } else {
1401 return -1; // nothing to do.
1402 }
1403 }
1404
1405 switch (m_model->m_completionOperator) {
1406 case T_LPAREN:
1407 if (completeConstructorOrFunction(results, endOfExpression, false))
1408 return m_positionForProposal;
1409 break;
1410
1411 case T_DOT:
1412 case T_ARROW:
1413 if (completeMember(results))
1414 return m_positionForProposal;
1415 break;
1416
1417 case T_COLON_COLON:
1418 if (completeScope(results))
1419 return m_positionForProposal;
1420 break;
1421
1422 case T_SIGNAL:
1423 if (completeQtMethod(results, CompleteQt4Signals))
1424 return m_positionForProposal;
1425 break;
1426
1427 case T_SLOT:
1428 if (completeQtMethod(results, CompleteQt4Slots))
1429 return m_positionForProposal;
1430 break;
1431
1432 case CompleteQt5SignalOrSlotClassNameTrigger:
1433 if (completeQtMethodClassName(results, scope) || globalCompletion(scope))
1434 return m_positionForProposal;
1435 break;
1436
1437 case CompleteQt5SignalTrigger:
1438 // Fallback to scope completion if "X::" is a namespace and not a class.
1439 if (completeQtMethod(results, CompleteQt5Signals) || completeScope(results))
1440 return m_positionForProposal;
1441 break;
1442
1443 case CompleteQt5SlotTrigger:
1444 // Fallback to scope completion if "X::" is a namespace and not a class.
1445 if (completeQtMethod(results, CompleteQt5Slots) || completeScope(results))
1446 return m_positionForProposal;
1447 break;
1448
1449 default:
1450 break;
1451 } // end of switch
1452
1453 // nothing to do.
1454 return -1;
1455 }
1456
globalCompletion(Scope * currentScope)1457 bool InternalCppCompletionAssistProcessor::globalCompletion(Scope *currentScope)
1458 {
1459 const LookupContext &context = m_model->m_typeOfExpression->context();
1460
1461 if (m_model->m_completionOperator == T_COLON_COLON) {
1462 completeNamespace(context.globalNamespace());
1463 return !m_completions.isEmpty();
1464 }
1465
1466 QList<ClassOrNamespace *> usingBindings;
1467 ClassOrNamespace *currentBinding = nullptr;
1468
1469 for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) {
1470 if (Block *block = scope->asBlock()) {
1471 if (ClassOrNamespace *binding = context.lookupType(scope)) {
1472 for (int i = 0; i < scope->memberCount(); ++i) {
1473 Symbol *member = scope->memberAt(i);
1474 if (member->isEnum()) {
1475 if (ClassOrNamespace *b = binding->findBlock(block))
1476 completeNamespace(b);
1477 }
1478 if (!member->name())
1479 continue;
1480 if (UsingNamespaceDirective *u = member->asUsingNamespaceDirective()) {
1481 if (ClassOrNamespace *b = binding->lookupType(u->name()))
1482 usingBindings.append(b);
1483 } else if (Class *c = member->asClass()) {
1484 if (c->name()->isAnonymousNameId()) {
1485 if (ClassOrNamespace *b = binding->findBlock(block))
1486 completeClass(b);
1487 }
1488 }
1489 }
1490 }
1491 } else if (scope->isFunction() || scope->isClass() || scope->isNamespace()) {
1492 currentBinding = context.lookupType(scope);
1493 break;
1494 }
1495 }
1496
1497 for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) {
1498 if (scope->isBlock()) {
1499 for (int i = 0; i < scope->memberCount(); ++i)
1500 addCompletionItem(scope->memberAt(i), FunctionLocalsOrder);
1501 } else if (Function *fun = scope->asFunction()) {
1502 for (int i = 0, argc = fun->argumentCount(); i < argc; ++i)
1503 addCompletionItem(fun->argumentAt(i), FunctionArgumentsOrder);
1504 } else if (Template *templ = scope->asTemplate()) {
1505 for (int i = 0, argc = templ->templateParameterCount(); i < argc; ++i)
1506 addCompletionItem(templ->templateParameterAt(i), FunctionArgumentsOrder);
1507 break;
1508 }
1509 }
1510
1511 QSet<ClassOrNamespace *> processed;
1512 for (; currentBinding; currentBinding = currentBinding->parent()) {
1513 if (processed.contains(currentBinding))
1514 break;
1515 processed.insert(currentBinding);
1516
1517 foreach (ClassOrNamespace* u, currentBinding->usings())
1518 usingBindings.append(u);
1519
1520 const QList<Symbol *> symbols = currentBinding->symbols();
1521
1522 if (!symbols.isEmpty()) {
1523 if (symbols.first()->isClass())
1524 completeClass(currentBinding);
1525 else
1526 completeNamespace(currentBinding);
1527 }
1528 }
1529
1530 foreach (ClassOrNamespace *b, usingBindings)
1531 completeNamespace(b);
1532
1533 addKeywords();
1534 addMacros(CppModelManager::configurationFileName(), context.snapshot());
1535 addMacros(context.thisDocument()->fileName(), context.snapshot());
1536 addSnippets();
1537 return !m_completions.isEmpty();
1538 }
1539
addKeywordCompletionItem(const QString & text)1540 void InternalCppCompletionAssistProcessor::addKeywordCompletionItem(const QString &text)
1541 {
1542 auto item = new CppAssistProposalItem;
1543 item->setText(text);
1544 item->setIcon(Icons::keywordIcon());
1545 item->setOrder(KeywordsOrder);
1546 item->setIsKeyword(true);
1547 m_completions.append(item);
1548 }
1549
completeMember(const QList<LookupItem> & baseResults)1550 bool InternalCppCompletionAssistProcessor::completeMember(const QList<LookupItem> &baseResults)
1551 {
1552 const LookupContext &context = m_model->m_typeOfExpression->context();
1553
1554 if (baseResults.isEmpty())
1555 return false;
1556
1557 ResolveExpression resolveExpression(context);
1558
1559 bool *replaceDotForArrow = nullptr;
1560 if (!m_interface->languageFeatures().objCEnabled)
1561 replaceDotForArrow = &m_model->m_replaceDotForArrow;
1562
1563 if (ClassOrNamespace *binding =
1564 resolveExpression.baseExpression(baseResults,
1565 m_model->m_completionOperator,
1566 replaceDotForArrow)) {
1567 if (binding)
1568 completeClass(binding, /*static lookup = */ true);
1569
1570 return !m_completions.isEmpty();
1571 }
1572
1573 return false;
1574 }
1575
completeScope(const QList<LookupItem> & results)1576 bool InternalCppCompletionAssistProcessor::completeScope(const QList<LookupItem> &results)
1577 {
1578 const LookupContext &context = m_model->m_typeOfExpression->context();
1579 if (results.isEmpty())
1580 return false;
1581
1582 foreach (const LookupItem &result, results) {
1583 FullySpecifiedType ty = result.type();
1584 Scope *scope = result.scope();
1585
1586 if (NamedType *namedTy = ty->asNamedType()) {
1587 if (ClassOrNamespace *b = context.lookupType(namedTy->name(), scope)) {
1588 completeClass(b);
1589 break;
1590 }
1591
1592 } else if (Class *classTy = ty->asClassType()) {
1593 if (ClassOrNamespace *b = context.lookupType(classTy)) {
1594 completeClass(b);
1595 break;
1596 }
1597
1598 // it can be class defined inside a block
1599 if (classTy->enclosingScope()->isBlock()) {
1600 if (ClassOrNamespace *b = context.lookupType(classTy->name(), classTy->enclosingScope())) {
1601 completeClass(b);
1602 break;
1603 }
1604 }
1605
1606 } else if (Namespace *nsTy = ty->asNamespaceType()) {
1607 if (ClassOrNamespace *b = context.lookupType(nsTy)) {
1608 completeNamespace(b);
1609 break;
1610 }
1611
1612 } else if (Template *templ = ty->asTemplateType()) {
1613 if (!result.binding())
1614 continue;
1615 if (ClassOrNamespace *b = result.binding()->lookupType(templ->name())) {
1616 completeClass(b);
1617 break;
1618 }
1619
1620 } else if (Enum *e = ty->asEnumType()) {
1621 // it can be class defined inside a block
1622 if (e->enclosingScope()->isBlock()) {
1623 if (ClassOrNamespace *b = context.lookupType(e)) {
1624 Block *block = e->enclosingScope()->asBlock();
1625 if (ClassOrNamespace *bb = b->findBlock(block)) {
1626 completeNamespace(bb);
1627 break;
1628 }
1629 }
1630 }
1631
1632 if (ClassOrNamespace *b = context.lookupType(e)) {
1633 completeNamespace(b);
1634 break;
1635 }
1636
1637 }
1638 }
1639
1640 return !m_completions.isEmpty();
1641 }
1642
completeNamespace(ClassOrNamespace * b)1643 void InternalCppCompletionAssistProcessor::completeNamespace(ClassOrNamespace *b)
1644 {
1645 QSet<ClassOrNamespace *> bindingsVisited;
1646 QList<ClassOrNamespace *> bindingsToVisit;
1647 bindingsToVisit.append(b);
1648
1649 while (!bindingsToVisit.isEmpty()) {
1650 ClassOrNamespace *binding = bindingsToVisit.takeFirst();
1651 if (!binding || bindingsVisited.contains(binding))
1652 continue;
1653
1654 bindingsVisited.insert(binding);
1655 bindingsToVisit += binding->usings();
1656
1657 QList<Scope *> scopesToVisit;
1658 QSet<Scope *> scopesVisited;
1659
1660 foreach (Symbol *bb, binding->symbols()) {
1661 if (Scope *scope = bb->asScope())
1662 scopesToVisit.append(scope);
1663 }
1664
1665 foreach (Enum *e, binding->unscopedEnums())
1666 scopesToVisit.append(e);
1667
1668 while (!scopesToVisit.isEmpty()) {
1669 Scope *scope = scopesToVisit.takeFirst();
1670 if (!scope || scopesVisited.contains(scope))
1671 continue;
1672
1673 scopesVisited.insert(scope);
1674
1675 for (Scope::iterator it = scope->memberBegin(); it != scope->memberEnd(); ++it) {
1676 Symbol *member = *it;
1677 addCompletionItem(member);
1678 }
1679 }
1680 }
1681 }
1682
completeClass(ClassOrNamespace * b,bool staticLookup)1683 void InternalCppCompletionAssistProcessor::completeClass(ClassOrNamespace *b, bool staticLookup)
1684 {
1685 QSet<ClassOrNamespace *> bindingsVisited;
1686 QList<ClassOrNamespace *> bindingsToVisit;
1687 bindingsToVisit.append(b);
1688
1689 while (!bindingsToVisit.isEmpty()) {
1690 ClassOrNamespace *binding = bindingsToVisit.takeFirst();
1691 if (!binding || bindingsVisited.contains(binding))
1692 continue;
1693
1694 bindingsVisited.insert(binding);
1695 bindingsToVisit += binding->usings();
1696
1697 QList<Scope *> scopesToVisit;
1698 QSet<Scope *> scopesVisited;
1699
1700 foreach (Symbol *bb, binding->symbols()) {
1701 if (Class *k = bb->asClass())
1702 scopesToVisit.append(k);
1703 else if (Block *b = bb->asBlock())
1704 scopesToVisit.append(b);
1705 }
1706
1707 foreach (Enum *e, binding->unscopedEnums())
1708 scopesToVisit.append(e);
1709
1710 while (!scopesToVisit.isEmpty()) {
1711 Scope *scope = scopesToVisit.takeFirst();
1712 if (!scope || scopesVisited.contains(scope))
1713 continue;
1714
1715 scopesVisited.insert(scope);
1716
1717 if (staticLookup)
1718 addCompletionItem(scope, InjectedClassNameOrder); // add a completion item for the injected class name.
1719
1720 addClassMembersToCompletion(scope, staticLookup);
1721 }
1722 }
1723 }
1724
addClassMembersToCompletion(Scope * scope,bool staticLookup)1725 void InternalCppCompletionAssistProcessor::addClassMembersToCompletion(Scope *scope,
1726 bool staticLookup)
1727 {
1728 if (!scope)
1729 return;
1730
1731 std::set<Class *> nestedAnonymouses;
1732
1733 for (Scope::iterator it = scope->memberBegin(); it != scope->memberEnd(); ++it) {
1734 Symbol *member = *it;
1735 if (member->isFriend()
1736 || member->isQtPropertyDeclaration()
1737 || member->isQtEnum()) {
1738 continue;
1739 } else if (!staticLookup && (member->isTypedef() ||
1740 member->isEnum() ||
1741 member->isClass())) {
1742 continue;
1743 } else if (member->isClass() && member->name()->isAnonymousNameId()) {
1744 nestedAnonymouses.insert(member->asClass());
1745 } else if (member->isDeclaration()) {
1746 Class *declTypeAsClass = member->asDeclaration()->type()->asClassType();
1747 if (declTypeAsClass && declTypeAsClass->name()->isAnonymousNameId())
1748 nestedAnonymouses.erase(declTypeAsClass);
1749 }
1750
1751 if (member->isPublic())
1752 addCompletionItem(member, PublicClassMemberOrder);
1753 else
1754 addCompletionItem(member);
1755 }
1756 for (Class *klass : nestedAnonymouses)
1757 addClassMembersToCompletion(klass, staticLookup);
1758 }
1759
completeQtMethod(const QList<LookupItem> & results,CompleteQtMethodMode type)1760 bool InternalCppCompletionAssistProcessor::completeQtMethod(const QList<LookupItem> &results,
1761 CompleteQtMethodMode type)
1762 {
1763 if (results.isEmpty())
1764 return false;
1765
1766 const LookupContext &context = m_model->m_typeOfExpression->context();
1767
1768 ConvertToCompletionItem toCompletionItem;
1769 Overview o;
1770 o.showReturnTypes = false;
1771 o.showArgumentNames = false;
1772 o.showFunctionSignatures = true;
1773
1774 QSet<QString> signatures;
1775 foreach (const LookupItem &lookupItem, results) {
1776 ClassOrNamespace *b = classOrNamespaceFromLookupItem(lookupItem, context);
1777 if (!b)
1778 continue;
1779
1780 QList<ClassOrNamespace *>todo;
1781 QSet<ClassOrNamespace *> processed;
1782 QList<Scope *> scopes;
1783 todo.append(b);
1784 while (!todo.isEmpty()) {
1785 ClassOrNamespace *binding = todo.takeLast();
1786 if (!processed.contains(binding)) {
1787 processed.insert(binding);
1788
1789 foreach (Symbol *s, binding->symbols())
1790 if (Class *clazz = s->asClass())
1791 scopes.append(clazz);
1792
1793 todo.append(binding->usings());
1794 }
1795 }
1796
1797 const bool wantSignals = type == CompleteQt4Signals || type == CompleteQt5Signals;
1798 const bool wantQt5SignalOrSlot = type == CompleteQt5Signals || type == CompleteQt5Slots;
1799 foreach (Scope *scope, scopes) {
1800 Class *klass = scope->asClass();
1801 if (!klass)
1802 continue;
1803
1804 for (int i = 0; i < scope->memberCount(); ++i) {
1805 Symbol *member = scope->memberAt(i);
1806 Function *fun = member->type()->asFunctionType();
1807 if (!fun || fun->isGenerated())
1808 continue;
1809 if (wantSignals && !fun->isSignal())
1810 continue;
1811 else if (!wantSignals && type == CompleteQt4Slots && !fun->isSlot())
1812 continue;
1813
1814 int count = fun->argumentCount();
1815 while (true) {
1816 const QString completionText = wantQt5SignalOrSlot
1817 ? createQt5SignalOrSlot(fun, o)
1818 : createQt4SignalOrSlot(fun, o);
1819
1820 if (!signatures.contains(completionText)) {
1821 AssistProposalItem *ci = toCompletionItem(fun);
1822 if (!ci)
1823 break;
1824 signatures.insert(completionText);
1825 ci->setText(completionText); // fix the completion item.
1826 ci->setIcon(Icons::iconForSymbol(fun));
1827 if (wantQt5SignalOrSlot && fun->isSlot())
1828 ci->setOrder(1);
1829 m_completions.append(ci);
1830 }
1831
1832 if (count && fun->argumentAt(count - 1)->asArgument()->hasInitializer())
1833 --count;
1834 else
1835 break;
1836 }
1837 }
1838 }
1839 }
1840
1841 return !m_completions.isEmpty();
1842 }
1843
completeQtMethodClassName(const QList<LookupItem> & results,Scope * cursorScope)1844 bool InternalCppCompletionAssistProcessor::completeQtMethodClassName(
1845 const QList<LookupItem> &results, Scope *cursorScope)
1846 {
1847 QTC_ASSERT(cursorScope, return false);
1848
1849 if (results.isEmpty())
1850 return false;
1851
1852 const LookupContext &context = m_model->m_typeOfExpression->context();
1853 const QIcon classIcon = Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Class);
1854 Overview overview;
1855
1856 foreach (const LookupItem &lookupItem, results) {
1857 Class *klass = classFromLookupItem(lookupItem, context);
1858 if (!klass)
1859 continue;
1860 const Name *name = minimalName(klass, cursorScope, context);
1861 QTC_ASSERT(name, continue);
1862
1863 addCompletionItem(overview.prettyName(name), classIcon);
1864 break;
1865 }
1866
1867 return !m_completions.isEmpty();
1868 }
1869
addKeywords()1870 void InternalCppCompletionAssistProcessor::addKeywords()
1871 {
1872 int keywordLimit = T_FIRST_OBJC_AT_KEYWORD;
1873 if (objcKeywordsWanted())
1874 keywordLimit = T_LAST_OBJC_AT_KEYWORD + 1;
1875
1876 // keyword completion items.
1877 for (int i = T_FIRST_KEYWORD; i < keywordLimit; ++i)
1878 addKeywordCompletionItem(QLatin1String(Token::name(i)));
1879
1880 // primitive type completion items.
1881 for (int i = T_FIRST_PRIMITIVE; i <= T_LAST_PRIMITIVE; ++i)
1882 addKeywordCompletionItem(QLatin1String(Token::name(i)));
1883
1884 // "Identifiers with special meaning"
1885 if (m_interface->languageFeatures().cxx11Enabled) {
1886 addKeywordCompletionItem(QLatin1String("override"));
1887 addKeywordCompletionItem(QLatin1String("final"));
1888 }
1889 }
1890
addMacros(const QString & fileName,const Snapshot & snapshot)1891 void InternalCppCompletionAssistProcessor::addMacros(const QString &fileName,
1892 const Snapshot &snapshot)
1893 {
1894 QSet<QString> processed;
1895 QSet<QString> definedMacros;
1896
1897 addMacros_helper(snapshot, fileName, &processed, &definedMacros);
1898
1899 foreach (const QString ¯oName, definedMacros)
1900 addCompletionItem(macroName, Icons::macroIcon(), MacrosOrder);
1901 }
1902
addMacros_helper(const Snapshot & snapshot,const QString & fileName,QSet<QString> * processed,QSet<QString> * definedMacros)1903 void InternalCppCompletionAssistProcessor::addMacros_helper(const Snapshot &snapshot,
1904 const QString &fileName,
1905 QSet<QString> *processed,
1906 QSet<QString> *definedMacros)
1907 {
1908 Document::Ptr doc = snapshot.document(fileName);
1909
1910 if (!doc || processed->contains(doc->fileName()))
1911 return;
1912
1913 processed->insert(doc->fileName());
1914
1915 foreach (const Document::Include &i, doc->resolvedIncludes())
1916 addMacros_helper(snapshot, i.resolvedFileName(), processed, definedMacros);
1917
1918 foreach (const CPlusPlus::Macro ¯o, doc->definedMacros()) {
1919 const QString macroName = macro.nameToQString();
1920 if (!macro.isHidden())
1921 definedMacros->insert(macroName);
1922 else
1923 definedMacros->remove(macroName);
1924 }
1925 }
1926
completeConstructorOrFunction(const QList<LookupItem> & results,int endOfExpression,bool toolTipOnly)1927 bool InternalCppCompletionAssistProcessor::completeConstructorOrFunction(const QList<LookupItem> &results,
1928 int endOfExpression,
1929 bool toolTipOnly)
1930 {
1931 const LookupContext &context = m_model->m_typeOfExpression->context();
1932 QList<Function *> functions;
1933
1934 foreach (const LookupItem &result, results) {
1935 FullySpecifiedType exprTy = result.type().simplified();
1936
1937 if (Class *klass = asClassOrTemplateClassType(exprTy)) {
1938 const Name *className = klass->name();
1939 if (!className)
1940 continue; // nothing to do for anonymous classes.
1941
1942 for (int i = 0; i < klass->memberCount(); ++i) {
1943 Symbol *member = klass->memberAt(i);
1944 const Name *memberName = member->name();
1945
1946 if (!memberName)
1947 continue; // skip anonymous member.
1948
1949 else if (memberName->isQualifiedNameId())
1950 continue; // skip
1951
1952 if (Function *funTy = member->type()->asFunctionType()) {
1953 if (memberName->match(className)) {
1954 // it's a ctor.
1955 functions.append(funTy);
1956 }
1957 }
1958 }
1959
1960 break;
1961 }
1962 }
1963
1964 if (functions.isEmpty()) {
1965 foreach (const LookupItem &result, results) {
1966 FullySpecifiedType ty = result.type().simplified();
1967
1968 if (Function *fun = asFunctionOrTemplateFunctionType(ty)) {
1969
1970 if (!fun->name()) {
1971 continue;
1972 } else if (!functions.isEmpty()
1973 && enclosingNonTemplateScope(functions.first())
1974 != enclosingNonTemplateScope(fun)) {
1975 continue; // skip fun, it's an hidden declaration.
1976 }
1977
1978 bool newOverload = true;
1979
1980 foreach (Function *f, functions) {
1981 if (fun->match(f)) {
1982 newOverload = false;
1983 break;
1984 }
1985 }
1986
1987 if (newOverload)
1988 functions.append(fun);
1989 }
1990 }
1991 }
1992
1993 if (functions.isEmpty()) {
1994 const Name *functionCallOp = context.bindings()->control()->operatorNameId(OperatorNameId::FunctionCallOp);
1995
1996 foreach (const LookupItem &result, results) {
1997 FullySpecifiedType ty = result.type().simplified();
1998 Scope *scope = result.scope();
1999
2000 if (NamedType *namedTy = ty->asNamedType()) {
2001 if (ClassOrNamespace *b = context.lookupType(namedTy->name(), scope)) {
2002 foreach (const LookupItem &r, b->lookup(functionCallOp)) {
2003 Symbol *overload = r.declaration();
2004 FullySpecifiedType overloadTy = overload->type().simplified();
2005
2006 if (Function *funTy = overloadTy->asFunctionType())
2007 functions.append(funTy);
2008 }
2009 }
2010 }
2011 }
2012 }
2013
2014 // There are two different kinds of completion we want to provide:
2015 // 1. If this is a function call, we want to pop up a tooltip that shows the user
2016 // the possible overloads with their argument types and names.
2017 // 2. If this is a function definition, we want to offer autocompletion of
2018 // the function signature.
2019
2020 // check if function signature autocompletion is appropriate
2021 // Also check if the function name is a destructor name.
2022 bool isDestructor = false;
2023 if (!functions.isEmpty() && !toolTipOnly) {
2024
2025 // function definitions will only happen in class or namespace scope,
2026 // so get the current location's enclosing scope.
2027
2028 // get current line and column
2029 int lineSigned = 0, columnSigned = 0;
2030 Utils::Text::convertPosition(m_interface->textDocument(), m_interface->position(),
2031 &lineSigned, &columnSigned);
2032 unsigned line = lineSigned, column = columnSigned - 1;
2033
2034 // find a scope that encloses the current location, starting from the lastVisibileSymbol
2035 // and moving outwards
2036
2037 Scope *sc = context.thisDocument()->scopeAt(line, column);
2038
2039 if (sc && (sc->isClass() || sc->isNamespace())) {
2040 // It may still be a function call. If the whole line parses as a function
2041 // declaration, we should be certain that it isn't.
2042 bool autocompleteSignature = false;
2043
2044 QTextCursor tc(m_interface->textDocument());
2045 tc.setPosition(endOfExpression);
2046 BackwardsScanner bs(tc, m_interface->languageFeatures());
2047 const int startToken = bs.startToken();
2048 int lineStartToken = bs.startOfLine(startToken);
2049 // make sure the required tokens are actually available
2050 bs.LA(startToken - lineStartToken);
2051 QString possibleDecl = bs.mid(lineStartToken).trimmed().append(QLatin1String("();"));
2052
2053 Document::Ptr doc = Document::create(QLatin1String("<completion>"));
2054 doc->setUtf8Source(possibleDecl.toUtf8());
2055 if (doc->parse(Document::ParseDeclaration)) {
2056 doc->check();
2057 if (SimpleDeclarationAST *sd = doc->translationUnit()->ast()->asSimpleDeclaration()) {
2058 if (sd->declarator_list && sd->declarator_list->value->postfix_declarator_list
2059 && sd->declarator_list->value->postfix_declarator_list->value->asFunctionDeclarator()) {
2060
2061 autocompleteSignature = true;
2062
2063 CoreDeclaratorAST *coreDecl = sd->declarator_list->value->core_declarator;
2064 if (coreDecl && coreDecl->asDeclaratorId() && coreDecl->asDeclaratorId()->name) {
2065 NameAST *declName = coreDecl->asDeclaratorId()->name;
2066 if (declName->asDestructorName()) {
2067 isDestructor = true;
2068 } else if (QualifiedNameAST *qName = declName->asQualifiedName()) {
2069 if (qName->unqualified_name && qName->unqualified_name->asDestructorName())
2070 isDestructor = true;
2071 }
2072 }
2073 }
2074 }
2075 }
2076
2077 if (autocompleteSignature && !isDestructor) {
2078 // set up for rewriting function types with minimally qualified names
2079 // to do it correctly we'd need the declaration's context and scope, but
2080 // that'd be too expensive to get here. instead, we just minimize locally
2081 SubstitutionEnvironment env;
2082 env.setContext(context);
2083 env.switchScope(sc);
2084 ClassOrNamespace *targetCoN = context.lookupType(sc);
2085 if (!targetCoN)
2086 targetCoN = context.globalNamespace();
2087 UseMinimalNames q(targetCoN);
2088 env.enter(&q);
2089 Control *control = context.bindings()->control().data();
2090
2091 // set up signature autocompletion
2092 foreach (Function *f, functions) {
2093 Overview overview;
2094 overview.showArgumentNames = true;
2095 overview.showDefaultArguments = false;
2096
2097 const FullySpecifiedType localTy = rewriteType(f->type(), &env, control);
2098
2099 // gets: "parameter list) cv-spec",
2100 const QString completion = overview.prettyType(localTy).mid(1);
2101 if (completion == QLatin1String(")"))
2102 continue;
2103
2104 addCompletionItem(completion, QIcon(), 0,
2105 QVariant::fromValue(CompleteFunctionDeclaration(f)));
2106 }
2107 return true;
2108 }
2109 }
2110 }
2111
2112 if (!functions.empty() && !isDestructor) {
2113 m_hintProposal = createHintProposal(functions);
2114 return true;
2115 }
2116
2117 return false;
2118 }
2119
getCppSpecifics() const2120 void CppCompletionAssistInterface::getCppSpecifics() const
2121 {
2122 if (m_gotCppSpecifics)
2123 return;
2124 m_gotCppSpecifics = true;
2125
2126 if (m_parser) {
2127 m_parser->update({CppTools::CppModelManager::instance()->workingCopy(),
2128 nullptr,
2129 Language::Cxx,
2130 false});
2131 m_snapshot = m_parser->snapshot();
2132 m_headerPaths = m_parser->headerPaths();
2133 }
2134 }
2135