cppcompletionassist.cpp 65.3 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
hjk's avatar
hjk committed
7
** Contact: Nokia Corporation (info@qt.nokia.com)
con's avatar
con committed
8
**
9
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
Tobias Hunger's avatar
Tobias Hunger committed
29
** Nokia at info@qt.nokia.com.
con's avatar
con committed
30
**
31
**************************************************************************/
hjk's avatar
hjk committed
32

con's avatar
con committed
33
#include "cppmodelmanager.h"
Leandro Melo's avatar
Leandro Melo committed
34
#include "cppcompletionassist.h"
35
#include "cppdoxygen.h"
Leandro Melo's avatar
Leandro Melo committed
36
#include "cppmodelmanager.h"
37
#include "cpptoolsconstants.h"
con's avatar
con committed
38
39
40
41
42
43
44
45
46
47
48
49

#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>
hjk's avatar
hjk committed
50

con's avatar
con committed
51
#include <cplusplus/ResolveExpression.h>
52
#include <cplusplus/MatchingText.h>
con's avatar
con committed
53
54
#include <cplusplus/Overview.h>
#include <cplusplus/ExpressionUnderCursor.h>
55
#include <cplusplus/BackwardsScanner.h>
56
#include <cplusplus/LookupContext.h>
con's avatar
con committed
57

Leandro Melo's avatar
Leandro Melo committed
58
#include <coreplugin/ifile.h>
con's avatar
con committed
59
#include <coreplugin/icore.h>
60
#include <coreplugin/mimedatabase.h>
Leandro Melo's avatar
Leandro Melo committed
61
62
63
64
65
66
67
#include <cppeditor/cppeditorconstants.h>
#include <texteditor/codeassist/basicproposalitem.h>
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
#include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/ifunctionhintproposalmodel.h>
#include <texteditor/codeassist/functionhintproposal.h>
#include <texteditor/convenience.h>
68
#include <texteditor/snippets/snippet.h>
Leandro Melo's avatar
Leandro Melo committed
69
70
#include <texteditor/texteditorsettings.h>
#include <texteditor/completionsettings.h>
con's avatar
con committed
71

Leandro Melo's avatar
Leandro Melo committed
72
73
74
75
#include <QtCore/QLatin1String>
#include <QtGui/QTextCursor>
#include <QtGui/QTextDocument>
#include <QtGui/QIcon>
76

con's avatar
con committed
77
using namespace CPlusPlus;
Leandro Melo's avatar
Leandro Melo committed
78
79
80
81
using namespace CppEditor;
using namespace CppTools;
using namespace Internal;
using namespace TextEditor;
con's avatar
con committed
82

Leandro Melo's avatar
Leandro Melo committed
83
namespace {
84

Leandro Melo's avatar
Leandro Melo committed
85
86
87
88
int activationSequenceChar(const QChar &ch,
                           const QChar &ch2,
                           const QChar &ch3,
                           unsigned *kind,
con's avatar
con committed
89
90
                           bool wantFunctionCall)
{
Leandro Melo's avatar
Leandro Melo committed
91
    int referencePosition = 0;
92
    int completionKind = T_EOF_SYMBOL;
93
94
95
    switch (ch.toLatin1()) {
    case '.':
        if (ch2 != QLatin1Char('.')) {
96
            completionKind = T_DOT;
Leandro Melo's avatar
Leandro Melo committed
97
            referencePosition = 1;
98
99
100
        }
        break;
    case ',':
101
        completionKind = T_COMMA;
Leandro Melo's avatar
Leandro Melo committed
102
        referencePosition = 1;
103
104
105
        break;
    case '(':
        if (wantFunctionCall) {
106
            completionKind = T_LPAREN;
Leandro Melo's avatar
Leandro Melo committed
107
            referencePosition = 1;
108
109
110
111
        }
        break;
    case ':':
        if (ch3 != QLatin1Char(':') && ch2 == QLatin1Char(':')) {
112
            completionKind = T_COLON_COLON;
Leandro Melo's avatar
Leandro Melo committed
113
            referencePosition = 2;
114
115
116
117
        }
        break;
    case '>':
        if (ch2 == QLatin1Char('-')) {
118
            completionKind = T_ARROW;
Leandro Melo's avatar
Leandro Melo committed
119
            referencePosition = 2;
120
121
122
123
        }
        break;
    case '*':
        if (ch2 == QLatin1Char('.')) {
124
            completionKind = T_DOT_STAR;
Leandro Melo's avatar
Leandro Melo committed
125
            referencePosition = 2;
126
        } else if (ch3 == QLatin1Char('-') && ch2 == QLatin1Char('>')) {
127
            completionKind = T_ARROW_STAR;
Leandro Melo's avatar
Leandro Melo committed
128
            referencePosition = 3;
129
130
131
132
133
        }
        break;
    case '\\':
    case '@':
        if (ch2.isNull() || ch2.isSpace()) {
134
            completionKind = T_DOXY_COMMENT;
Leandro Melo's avatar
Leandro Melo committed
135
            referencePosition = 1;
136
137
138
        }
        break;
    case '<':
139
        completionKind = T_ANGLE_STRING_LITERAL;
Leandro Melo's avatar
Leandro Melo committed
140
        referencePosition = 1;
141
142
        break;
    case '"':
143
        completionKind = T_STRING_LITERAL;
Leandro Melo's avatar
Leandro Melo committed
144
        referencePosition = 1;
145
146
        break;
    case '/':
147
        completionKind = T_SLASH;
Leandro Melo's avatar
Leandro Melo committed
148
        referencePosition = 1;
149
        break;
150
151
    case '#':
        completionKind = T_POUND;
Leandro Melo's avatar
Leandro Melo committed
152
        referencePosition = 1;
153
        break;
con's avatar
con committed
154
155
    }

Leandro Melo's avatar
Leandro Melo committed
156
157
    if (kind)
        *kind = completionKind;
158

Leandro Melo's avatar
Leandro Melo committed
159
160
    return referencePosition;
}
161

Leandro Melo's avatar
Leandro Melo committed
162
} // Anonymous
163

Leandro Melo's avatar
Leandro Melo committed
164
165
namespace CppTools {
namespace Internal {
166

Leandro Melo's avatar
Leandro Melo committed
167
168
169
170
171
struct CompleteFunctionDeclaration
{
    explicit CompleteFunctionDeclaration(Function *f = 0)
        : function(f)
    {}
172

Leandro Melo's avatar
Leandro Melo committed
173
174
    Function *function;
};
175

Leandro Melo's avatar
Leandro Melo committed
176
177
178
179
180
181
182
183
184
185
186
// ----------------------
// CppAssistProposalModel
// ----------------------
class CppAssistProposalModel : public TextEditor::BasicProposalItemListModel
{
public:
    CppAssistProposalModel()
        : TextEditor::BasicProposalItemListModel()
        , m_sortable(false)
        , m_completionOperator(T_EOF_SYMBOL)
        , m_replaceDotForArrow(false)
187
        , m_typeOfExpression(new TypeOfExpression)
Leandro Melo's avatar
Leandro Melo committed
188
    {}
189

Leandro Melo's avatar
Leandro Melo committed
190
191
    virtual bool isSortable() const { return m_sortable; }
    virtual IAssistProposalItem *proposalItem(int index) const;
con's avatar
con committed
192

Leandro Melo's avatar
Leandro Melo committed
193
194
195
    bool m_sortable;
    unsigned m_completionOperator;
    bool m_replaceDotForArrow;
196
    QSharedPointer<TypeOfExpression> m_typeOfExpression;
Leandro Melo's avatar
Leandro Melo committed
197
};
198

Leandro Melo's avatar
Leandro Melo committed
199
200
201
202
// ---------------------
// CppAssistProposalItem
// ---------------------
class CppAssistProposalItem : public TextEditor::BasicProposalItem
203
{
Leandro Melo's avatar
Leandro Melo committed
204
public:
205
    CppAssistProposalItem() :
206
        m_isOverloaded(false) {}
207

Leandro Melo's avatar
Leandro Melo committed
208
209
210
    virtual bool prematurelyApplies(const QChar &c) const;
    virtual void applyContextualContent(TextEditor::BaseTextEditor *editor,
                                        int basePosition) const;
211

Leandro Melo's avatar
Leandro Melo committed
212
213
214
    bool isOverloaded() const { return m_isOverloaded; }
    void markAsOverloaded() { m_isOverloaded = true; }
    void keepCompletionOperator(unsigned compOp) { m_completionOperator = compOp; }
215
216
    void keepTypeOfExpression(const QSharedPointer<TypeOfExpression> &typeOfExp)
    { m_typeOfExpression = typeOfExp; }
217

Leandro Melo's avatar
Leandro Melo committed
218
219
220
221
private:
    bool m_isOverloaded;
    unsigned m_completionOperator;
    mutable QChar m_typedChar;
222
    QSharedPointer<TypeOfExpression> m_typeOfExpression;
Leandro Melo's avatar
Leandro Melo committed
223
};
224

Leandro Melo's avatar
Leandro Melo committed
225
226
} // Internal
} // CppTools
227

Leandro Melo's avatar
Leandro Melo committed
228
Q_DECLARE_METATYPE(CppTools::Internal::CompleteFunctionDeclaration)
229

Leandro Melo's avatar
Leandro Melo committed
230
231
232
233
234
235
236
IAssistProposalItem *CppAssistProposalModel::proposalItem(int index) const
{
    BasicProposalItem *item =
        static_cast<BasicProposalItem *>(BasicProposalItemListModel::proposalItem(index));
    if (!item->data().canConvert<QString>()) {
        CppAssistProposalItem *cppItem = static_cast<CppAssistProposalItem *>(item);
        cppItem->keepCompletionOperator(m_completionOperator);
237
        cppItem->keepTypeOfExpression(m_typeOfExpression);
Leandro Melo's avatar
Leandro Melo committed
238
239
240
    }
    return item;
}
241

Leandro Melo's avatar
Leandro Melo committed
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
bool CppAssistProposalItem::prematurelyApplies(const QChar &typedChar) const
{
    if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
        if (typedChar == QLatin1Char('(') || typedChar == QLatin1Char(',')) {
            m_typedChar = typedChar;
            return true;
        }
    } else if (m_completionOperator == T_STRING_LITERAL
               || m_completionOperator == T_ANGLE_STRING_LITERAL) {
        if (typedChar == QLatin1Char('/') && text().endsWith(QLatin1Char('/'))) {
            m_typedChar = typedChar;
            return true;
        }
    } else if (data().value<Symbol *>()) {
        if (typedChar == QLatin1Char(':')
                || typedChar == QLatin1Char(';')
                || typedChar == QLatin1Char('.')
                || typedChar == QLatin1Char(',')
                || typedChar == QLatin1Char('(')) {
            m_typedChar = typedChar;
            return true;
        }
    } else if (data().canConvert<CompleteFunctionDeclaration>()) {
        if (typedChar == QLatin1Char('(')) {
            m_typedChar = typedChar;
            return true;
        }
    }

    return false;
}

void CppAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor *editor,
                                                    int basePosition) const
{
    Symbol *symbol = 0;

    if (data().isValid())
        symbol = data().value<Symbol *>();

    QString toInsert;
    QString extraChars;
    int extraLength = 0;
    int cursorOffset = 0;

    bool autoParenthesesEnabled = true;

    if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) {
        toInsert = text();
        extraChars += QLatin1Char(')');

        if (m_typedChar == QLatin1Char('(')) // Eat the opening parenthesis
            m_typedChar = QChar();
    } else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) {
        toInsert = text();
        if (!toInsert.endsWith(QLatin1Char('/'))) {
            extraChars += QLatin1Char((m_completionOperator == T_ANGLE_STRING_LITERAL) ? '>' : '"');
        } else {
            if (m_typedChar == QLatin1Char('/')) // Eat the slash
                m_typedChar = QChar();
        }
    } else {
        toInsert = text();

        const CompletionSettings &completionSettings =
                TextEditorSettings::instance()->completionSettings();
        const bool autoInsertBrackets = completionSettings.m_autoInsertBrackets;

        if (autoInsertBrackets && symbol && symbol->type()) {
            if (Function *function = symbol->type()->asFunctionType()) {
                // If the member is a function, automatically place the opening parenthesis,
                // except when it might take template parameters.
                if (! function->hasReturnType() && (function->unqualifiedName() && !function->unqualifiedName()->isDestructorNameId())) {
                    // Don't insert any magic, since the user might have just wanted to select the class

                    /// ### port me
#if 0
                } else if (function->templateParameterCount() != 0 && typedChar != QLatin1Char('(')) {
                    // If there are no arguments, then we need the template specification
                    if (function->argumentCount() == 0) {
                        extraChars += QLatin1Char('<');
                    }
#endif
                } else if (! function->isAmbiguous()) {
                    // When the user typed the opening parenthesis, he'll likely also type the closing one,
                    // in which case it would be annoying if we put the cursor after the already automatically
                    // inserted closing parenthesis.
                    const bool skipClosingParenthesis = m_typedChar != QLatin1Char('(');

                    if (completionSettings.m_spaceAfterFunctionName)
                        extraChars += QLatin1Char(' ');
                    extraChars += QLatin1Char('(');
                    if (m_typedChar == QLatin1Char('('))
                        m_typedChar = QChar();

                    // If the function doesn't return anything, automatically place the semicolon,
                    // unless we're doing a scope completion (then it might be function definition).
                    const QChar characterAtCursor = editor->characterAt(editor->position());
                    bool endWithSemicolon = m_typedChar == QLatin1Char(';')
                            || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON);
                    const QChar semicolon = m_typedChar.isNull() ? QLatin1Char(';') : m_typedChar;

                    if (endWithSemicolon && characterAtCursor == semicolon) {
                        endWithSemicolon = false;
                        m_typedChar = QChar();
                    }

                    // If the function takes no arguments, automatically place the closing parenthesis
                    if (!isOverloaded() && ! function->hasArguments() && skipClosingParenthesis) {
                        extraChars += QLatin1Char(')');
                        if (endWithSemicolon) {
                            extraChars += semicolon;
                            m_typedChar = QChar();
                        }
                    } else if (autoParenthesesEnabled) {
                        const QChar lookAhead = editor->characterAt(editor->position() + 1);
                        if (MatchingText::shouldInsertMatchingText(lookAhead)) {
                            extraChars += QLatin1Char(')');
                            --cursorOffset;
                            if (endWithSemicolon) {
                                extraChars += semicolon;
                                --cursorOffset;
                                m_typedChar = QChar();
                            }
                        }
                        // TODO: When an opening parenthesis exists, the "semicolon" should really be
                        // inserted after the matching closing parenthesis.
                    }
                }
            }
        }

        if (autoInsertBrackets && data().canConvert<CompleteFunctionDeclaration>()) {
            if (m_typedChar == QLatin1Char('('))
                m_typedChar = QChar();

            // everything from the closing parenthesis on are extra chars, to
            // make sure an auto-inserted ")" gets replaced by ") const" if necessary
            int closingParen = toInsert.lastIndexOf(QLatin1Char(')'));
            extraChars = toInsert.mid(closingParen);
            toInsert.truncate(closingParen);
        }
    }

    // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before
    if (!m_typedChar.isNull()) {
        extraChars += m_typedChar;
        if (cursorOffset != 0)
            --cursorOffset;
    }

    // Avoid inserting characters that are already there
    for (int i = 0; i < extraChars.length(); ++i) {
        const QChar a = extraChars.at(i);
        const QChar b = editor->characterAt(editor->position() + i);
        if (a == b)
            ++extraLength;
        else
            break;
    }

    toInsert += extraChars;

    // Insert the remainder of the name
    int length = editor->position() - basePosition + extraLength;
    editor->setCursorPosition(basePosition);
    editor->replace(length, toInsert);
    if (cursorOffset)
        editor->setCursorPosition(editor->position() + cursorOffset);
}

// --------------------
// CppFunctionHintModel
// --------------------
class CppFunctionHintModel : public TextEditor::IFunctionHintProposalModel
{
public:
419
420
    CppFunctionHintModel(QList<Function *> functionSymbols,
                         const QSharedPointer<TypeOfExpression> &typeOfExp)
Leandro Melo's avatar
Leandro Melo committed
421
422
        : m_functionSymbols(functionSymbols)
        , m_currentArg(-1)
423
        , m_typeOfExpression(typeOfExp)
Leandro Melo's avatar
Leandro Melo committed
424
425
426
427
428
429
430
431
432
433
    {}

    virtual void reset() {}
    virtual int size() const { return m_functionSymbols.size(); }
    virtual QString text(int index) const;
    virtual int activeArgument(const QString &prefix) const;

private:
    QList<Function *> m_functionSymbols;
    mutable int m_currentArg;
434
    QSharedPointer<TypeOfExpression> m_typeOfExpression;
Leandro Melo's avatar
Leandro Melo committed
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
};

QString CppFunctionHintModel::text(int index) const
{
    Overview overview;
    overview.setShowReturnTypes(true);
    overview.setShowArgumentNames(true);
    overview.setMarkedArgument(m_currentArg + 1);
    Function *f = m_functionSymbols.at(index);

    const QString prettyMethod = overview(f->type(), f->name());
    const int begin = overview.markedArgumentBegin();
    const int end = overview.markedArgumentEnd();

    QString hintText;
    hintText += Qt::escape(prettyMethod.left(begin));
    hintText += "<b>";
    hintText += Qt::escape(prettyMethod.mid(begin, end - begin));
    hintText += "</b>";
    hintText += Qt::escape(prettyMethod.mid(end));
    return hintText;
}

int CppFunctionHintModel::activeArgument(const QString &prefix) const
{
    int argnr = 0;
    int parcount = 0;
    SimpleLexer tokenize;
    QList<Token> tokens = tokenize(prefix);
    for (int i = 0; i < tokens.count(); ++i) {
        const Token &tk = tokens.at(i);
        if (tk.is(T_LPAREN))
            ++parcount;
        else if (tk.is(T_RPAREN))
            --parcount;
        else if (! parcount && tk.is(T_COMMA))
            ++argnr;
    }

    if (parcount < 0)
        return -1;

    if (argnr != m_currentArg)
        m_currentArg = argnr;

    return argnr;
}

// ---------------------------
// CppCompletionAssistProvider
// ---------------------------
bool CppCompletionAssistProvider::supportsEditor(const QString &editorId) const
{
    return editorId == QLatin1String(CppEditor::Constants::CPPEDITOR_ID);
}

int CppCompletionAssistProvider::activationCharSequenceLength() const
{
    return 3;
}

bool CppCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
{
    const QChar &ch  = sequence.at(2);
    const QChar &ch2 = sequence.at(1);
    const QChar &ch3 = sequence.at(0);
    if (activationSequenceChar(ch, ch2, ch3, 0, true) != 0)
        return true;
    return false;
}

IAssistProcessor *CppCompletionAssistProvider::createProcessor() const
{
    return new CppCompletionAssistProcessor;
}

// -----------------
// CppAssistProposal
// -----------------
class CppAssistProposal : public TextEditor::GenericProposal
{
public:
    CppAssistProposal(int cursorPos, TextEditor::IGenericProposalModel *model)
        : TextEditor::GenericProposal(cursorPos, model)
        , m_replaceDotForArrow(static_cast<CppAssistProposalModel *>(model)->m_replaceDotForArrow)
    {}

    virtual bool isCorrective() const { return m_replaceDotForArrow; }
    virtual void makeCorrection(BaseTextEditor *editor);

private:
    bool m_replaceDotForArrow;
};

void CppAssistProposal::makeCorrection(BaseTextEditor *editor)
{
    editor->setCursorPosition(basePosition() - 1);
    editor->replace(1, QLatin1String("->"));
    moveBasePosition(1);
}

namespace {

class ConvertToCompletionItem: protected NameVisitor
{
    // The completion item.
    BasicProposalItem *_item;

    // The current symbol.
    Symbol *_symbol;

    // The pretty printer.
    Overview overview;

public:
    ConvertToCompletionItem()
        : _item(0)
        , _symbol(0)
    { }

    BasicProposalItem *operator()(Symbol *symbol)
    {
        if (! symbol || ! symbol->name() || symbol->name()->isQualifiedNameId())
            return 0;

        BasicProposalItem *previousItem = switchCompletionItem(0);
        Symbol *previousSymbol = switchSymbol(symbol);
        accept(symbol->unqualifiedName());
        if (_item)
            _item->setData(QVariant::fromValue(symbol));
        (void) switchSymbol(previousSymbol);
        return switchCompletionItem(previousItem);
    }

protected:
    Symbol *switchSymbol(Symbol *symbol)
    {
        Symbol *previousSymbol = _symbol;
        _symbol = symbol;
        return previousSymbol;
    }

    BasicProposalItem *switchCompletionItem(BasicProposalItem *item)
    {
        BasicProposalItem *previousItem = _item;
        _item = item;
        return previousItem;
    }

    BasicProposalItem *newCompletionItem(const Name *name)
    {
        BasicProposalItem *item = new CppAssistProposalItem;
        item->setText(overview.prettyName(name));
        return item;
    }

    virtual void visit(const Identifier *name)
    { _item = newCompletionItem(name); }

    virtual void visit(const TemplateNameId *name)
    {
        _item = newCompletionItem(name);
        _item->setText(QLatin1String(name->identifier()->chars()));
    }

    virtual void visit(const DestructorNameId *name)
    { _item = newCompletionItem(name); }

    virtual void visit(const OperatorNameId *name)
    { _item = newCompletionItem(name); }

    virtual void visit(const ConversionNameId *name)
    { _item = newCompletionItem(name); }

    virtual void visit(const QualifiedNameId *name)
    { _item = newCompletionItem(name->name()); }
};

Class *asClassOrTemplateClassType(FullySpecifiedType ty)
{
    if (Class *classTy = ty->asClassType())
        return classTy;
    else if (Template *templ = ty->asTemplateType()) {
        if (Symbol *decl = templ->declaration())
            return decl->asClass();
    }
    return 0;
}

Scope *enclosingNonTemplateScope(Symbol *symbol)
{
    if (symbol) {
        if (Scope *scope = symbol->enclosingScope()) {
            if (Template *templ = scope->asTemplate())
                return templ->enclosingScope();
            return scope;
        }
    }
    return 0;
}

Function *asFunctionOrTemplateFunctionType(FullySpecifiedType ty)
{
    if (Function *funTy = ty->asFunctionType())
        return funTy;
    else if (Template *templ = ty->asTemplateType()) {
        if (Symbol *decl = templ->declaration())
            return decl->asFunction();
    }
    return 0;
}

} // Anonymous

// ----------------------------
// CppCompletionAssistProcessor
// ----------------------------
CppCompletionAssistProcessor::CppCompletionAssistProcessor()
    : m_startPosition(-1)
    , m_objcEnabled(true)
    , m_snippetCollector(CppEditor::Constants::CPP_SNIPPETS_GROUP_ID,
                         QIcon(QLatin1String(":/texteditor/images/snippet.png")))
    , preprocessorCompletions(QStringList()
          << QLatin1String("define")
          << QLatin1String("error")
          << QLatin1String("include")
          << QLatin1String("line")
          << QLatin1String("pragma")
          << QLatin1String("undef")
          << QLatin1String("if")
          << QLatin1String("ifdef")
          << QLatin1String("ifndef")
          << QLatin1String("elif")
          << QLatin1String("else")
          << QLatin1String("endif"))
    , m_model(new CppAssistProposalModel)
    , m_hintProposal(0)
{}

CppCompletionAssistProcessor::~CppCompletionAssistProcessor()
{}

IAssistProposal * CppCompletionAssistProcessor::perform(const IAssistInterface *interface)
{
    m_interface.reset(static_cast<const CppCompletionAssistInterface *>(interface));

    if (interface->reason() != ExplicitlyInvoked && !accepts())
        return 0;

    int index = startCompletionHelper();
    if (index != -1) {
        if (m_hintProposal)
            return m_hintProposal;

        if (m_model->m_completionOperator != T_EOF_SYMBOL)
            m_model->m_sortable = true;
        else
            m_model->m_sortable = false;
        return createContentProposal();
    }

    return 0;
}

bool CppCompletionAssistProcessor::accepts() const
{
    const int pos = m_interface->position();
    unsigned token = T_EOF_SYMBOL;

    const int start = startOfOperator(pos, &token, /*want function call=*/ true);
    if (start != pos) {
        if (token == T_POUND) {
            const int column = pos - m_interface->document()->findBlock(start).position();
            if (column != 1)
                return false;
        }

        return true;
    } else {
        // Trigger completion after three characters of a name have been typed, when not editing an existing name
        QChar characterUnderCursor = m_interface->characterAt(pos);
716
        if (!characterUnderCursor.isLetterOrNumber() && characterUnderCursor != QLatin1Char('_')) {
717
            const int startOfName = findStartOfName(pos);
718
            if (pos - startOfName >= 3) {
Leandro Melo's avatar
Leandro Melo committed
719
                const QChar firstCharacter = m_interface->characterAt(startOfName);
720
721
                if (firstCharacter.isLetter() || firstCharacter == QLatin1Char('_')) {
                    // Finally check that we're not inside a comment or string (code copied from startOfOperator)
Leandro Melo's avatar
Leandro Melo committed
722
                    QTextCursor tc(m_interface->document());
723
724
725
726
727
728
729
730
731
732
                    tc.setPosition(pos);

                    SimpleLexer tokenize;
                    tokenize.setQtMocRunEnabled(true);
                    tokenize.setObjCEnabled(true);
                    tokenize.setSkipComments(false);
                    const QList<Token> &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
                    const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1));
                    const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);

733
                    if (!tk.isComment() && !tk.isLiteral()) {
734
                        return true;
735
736
737
738
739
740
741
742
743
744
745
746
747
748
                    } else if (tk.isLiteral()
                               && tokens.size() == 3
                               && tokens.at(0).kind() == T_POUND
                               && tokens.at(1).kind() == T_IDENTIFIER) {
                        const QString &line = tc.block().text();
                        const Token &idToken = tokens.at(1);
                        const QStringRef &identifier =
                                line.midRef(idToken.begin(), idToken.end() - idToken.begin());
                        if (identifier == QLatin1String("include")
                                || identifier == QLatin1String("include_next")
                                || (m_objcEnabled && identifier == QLatin1String("import"))) {
                            return true;
                        }
                    }
749
750
                }
            }
751
        }
752
    }
con's avatar
con committed
753
754
755
756

    return false;
}

Leandro Melo's avatar
Leandro Melo committed
757
IAssistProposal *CppCompletionAssistProcessor::createContentProposal()
con's avatar
con committed
758
{
Leandro Melo's avatar
Leandro Melo committed
759
760
761
762
763
764
765
766
767
768
769
770
771
772
    // Duplicates are kept only if they are snippets.
    QSet<QString> processed;
    QList<BasicProposalItem *>::iterator it = m_completions.begin();
    while (it != m_completions.end()) {
        CppAssistProposalItem *item = static_cast<CppAssistProposalItem *>(*it);
        if (!processed.contains(item->text()) || item->data().canConvert<QString>()) {
            ++it;
            if (!item->data().canConvert<QString>()) {
                processed.insert(item->text());
                if (!item->isOverloaded()) {
                    if (Symbol *symbol = qvariant_cast<Symbol *>(item->data())) {
                        if (Function *funTy = symbol->type()->asFunctionType()) {
                            if (funTy->hasArguments())
                                item->markAsOverloaded();
773
                        }
Leandro Melo's avatar
Leandro Melo committed
774
                    }
775
776
                }
            }
Leandro Melo's avatar
Leandro Melo committed
777
        } else {
778
            delete *it;
Leandro Melo's avatar
Leandro Melo committed
779
            it = m_completions.erase(it);
780
        }
Roberto Raggi's avatar
Roberto Raggi committed
781
    }
782

Leandro Melo's avatar
Leandro Melo committed
783
784
785
    m_model->loadContent(m_completions);
    return new CppAssistProposal(m_startPosition, m_model.take());
}
786

Leandro Melo's avatar
Leandro Melo committed
787
788
789
IAssistProposal *CppCompletionAssistProcessor::createHintProposal(
    QList<CPlusPlus::Function *> functionSymbols) const
{
790
791
    IFunctionHintProposalModel *model =
            new CppFunctionHintModel(functionSymbols, m_model->m_typeOfExpression);
Leandro Melo's avatar
Leandro Melo committed
792
793
    IAssistProposal *proposal = new FunctionHintProposal(m_startPosition, model);
    return proposal;
794
795
}

Leandro Melo's avatar
Leandro Melo committed
796
797
798
int CppCompletionAssistProcessor::startOfOperator(int pos,
                                                  unsigned *kind,
                                                  bool wantFunctionCall) const
799
{
Leandro Melo's avatar
Leandro Melo committed
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
    const QChar ch  = pos > -1 ? m_interface->characterAt(pos - 1) : QChar();
    const QChar ch2 = pos >  0 ? m_interface->characterAt(pos - 2) : QChar();
    const QChar ch3 = pos >  1 ? m_interface->characterAt(pos - 3) : QChar();

    int start = pos - activationSequenceChar(ch, ch2, ch3, kind, wantFunctionCall);
    if (start != pos) {
        QTextCursor tc(m_interface->document());
        tc.setPosition(pos);

        // Include completion: make sure the quote character is the first one on the line
        if (*kind == T_STRING_LITERAL) {
            QTextCursor s = tc;
            s.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
            QString sel = s.selectedText();
            if (sel.indexOf(QLatin1Char('"')) < sel.length() - 1) {
                *kind = T_EOF_SYMBOL;
                start = pos;
            }
        }
819

Leandro Melo's avatar
Leandro Melo committed
820
821
822
823
824
825
826
        if (*kind == T_COMMA) {
            ExpressionUnderCursor expressionUnderCursor;
            if (expressionUnderCursor.startOfFunctionCall(tc) == -1) {
                *kind = T_EOF_SYMBOL;
                start = pos;
            }
        }
827

Leandro Melo's avatar
Leandro Melo committed
828
829
830
831
832
833
834
        SimpleLexer tokenize;
        tokenize.setQtMocRunEnabled(true);
        tokenize.setObjCEnabled(true);
        tokenize.setSkipComments(false);
        const QList<Token> &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
        const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor
        const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
835

Leandro Melo's avatar
Leandro Melo committed
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
        if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) {
            *kind = T_EOF_SYMBOL;
            start = pos;
        }
        // Don't complete in comments or strings, but still check for include completion
        else if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT) ||
                 (tk.isLiteral() && (*kind != T_STRING_LITERAL
                                     && *kind != T_ANGLE_STRING_LITERAL
                                     && *kind != T_SLASH))) {
            *kind = T_EOF_SYMBOL;
            start = pos;
        }
        // Include completion: can be triggered by slash, but only in a string
        else if (*kind == T_SLASH && (tk.isNot(T_STRING_LITERAL) && tk.isNot(T_ANGLE_STRING_LITERAL))) {
            *kind = T_EOF_SYMBOL;
            start = pos;
        }
        else if (*kind == T_LPAREN) {
            if (tokenIdx > 0) {
                const Token &previousToken = tokens.at(tokenIdx - 1); // look at the token at the left of T_LPAREN
                switch (previousToken.kind()) {
                case T_IDENTIFIER:
                case T_GREATER:
                case T_SIGNAL:
                case T_SLOT:
                    break; // good

                default:
                    // that's a bad token :)
                    *kind = T_EOF_SYMBOL;
                    start = pos;
                }
            }
        }
        // Check for include preprocessor directive
        else if (*kind == T_STRING_LITERAL || *kind == T_ANGLE_STRING_LITERAL || *kind == T_SLASH) {
            bool include = false;
            if (tokens.size() >= 3) {
                if (tokens.at(0).is(T_POUND) && tokens.at(1).is(T_IDENTIFIER) && (tokens.at(2).is(T_STRING_LITERAL) ||
                                                                                  tokens.at(2).is(T_ANGLE_STRING_LITERAL))) {
                    const Token &directiveToken = tokens.at(1);
                    QString directive = tc.block().text().mid(directiveToken.begin(),
                                                              directiveToken.length());
                    if (directive == QLatin1String("include") ||
                            directive == QLatin1String("include_next") ||
                            directive == QLatin1String("import")) {
                        include = true;
883
884
885
                    }
                }
            }
Leandro Melo's avatar
Leandro Melo committed
886
887
888
889
890

            if (!include) {
                *kind = T_EOF_SYMBOL;
                start = pos;
            }
891
892
        }
    }
Leandro Melo's avatar
Leandro Melo committed
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908

    return start;
}

int CppCompletionAssistProcessor::findStartOfName(int pos) const
{
    if (pos == -1)
        pos = m_interface->position();
    QChar chr;

    // Skip to the start of a name
    do {
        chr = m_interface->characterAt(--pos);
    } while (chr.isLetterOrNumber() || chr == QLatin1Char('_'));

    return pos + 1;
909
910
}

Leandro Melo's avatar
Leandro Melo committed
911
int CppCompletionAssistProcessor::startCompletionHelper()
912
{
Leandro Melo's avatar
Leandro Melo committed
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
    if (m_objcEnabled) {
        if (tryObjCCompletion())
            return m_startPosition;
    }

    const int startOfName = findStartOfName();
    m_startPosition = startOfName;
    m_model->m_completionOperator = T_EOF_SYMBOL;

    int endOfOperator = m_startPosition;

    // Skip whitespace preceding this position
    while (m_interface->characterAt(endOfOperator - 1).isSpace())
        --endOfOperator;

    int endOfExpression = startOfOperator(endOfOperator,
                                          &m_model->m_completionOperator,
                                          /*want function call =*/ true);

    const Core::IFile *file = m_interface->file();
    QString fileName = file->fileName();

    if (m_model->m_completionOperator == T_DOXY_COMMENT) {
        for (int i = 1; i < T_DOXY_LAST_TAG; ++i)
            addCompletionItem(QString::fromLatin1(doxygenTagSpell(i)), m_icons.keywordIcon());
        return m_startPosition;
    }

    // Pre-processor completion
    if (m_model->m_completionOperator == T_POUND) {
        completePreprocessor();
        m_startPosition = startOfName;
        return m_startPosition;
    }

    // Include completion
    if (m_model->m_completionOperator == T_STRING_LITERAL
        || m_model->m_completionOperator == T_ANGLE_STRING_LITERAL
        || m_model->m_completionOperator == T_SLASH) {

        QTextCursor c(m_interface->document());
        c.setPosition(endOfExpression);
        if (completeInclude(c))
            m_startPosition = startOfName;
        return m_startPosition;
    }

    ExpressionUnderCursor expressionUnderCursor;
    QTextCursor tc(m_interface->document());

    if (m_model->m_completionOperator == T_COMMA) {
        tc.setPosition(endOfExpression);
        const int start = expressionUnderCursor.startOfFunctionCall(tc);
        if (start == -1) {
            m_model->m_completionOperator = T_EOF_SYMBOL;
            return -1;
        }

        endOfExpression = start;
        m_startPosition = start + 1;
        m_model->m_completionOperator = T_LPAREN;
    }

    QString expression;
    int startOfExpression = m_interface->position();
    tc.setPosition(endOfExpression);

    if (m_model->m_completionOperator) {
        expression = expressionUnderCursor(tc);
        startOfExpression = endOfExpression - expression.length();

        if (m_model->m_completionOperator == T_LPAREN) {
            if (expression.endsWith(QLatin1String("SIGNAL")))
                m_model->m_completionOperator = T_SIGNAL;

            else if (expression.endsWith(QLatin1String("SLOT")))
                m_model->m_completionOperator = T_SLOT;

            else if (m_interface->position() != endOfOperator) {
                // We don't want a function completion when the cursor isn't at the opening brace
                expression.clear();
                m_model->m_completionOperator = T_EOF_SYMBOL;
                m_startPosition = startOfName;
                startOfExpression = m_interface->position();
            }
        }
    } else if (expression.isEmpty()) {
        while (startOfExpression > 0 && m_interface->characterAt(startOfExpression).isSpace())
            --startOfExpression;
    }

    int line = 0, column = 0;
    Convenience::convertPosition(m_interface->document(), startOfExpression, &line, &column);
    return startCompletionInternal(fileName, line, column, expression, endOfExpression);
}
1008

Leandro Melo's avatar
Leandro Melo committed
1009
1010
1011
1012
bool CppCompletionAssistProcessor::tryObjCCompletion()
{
    int end = m_interface->position();
    while (m_interface->characterAt(end).isSpace())
1013
        ++end;
Leandro Melo's avatar
Leandro Melo committed
1014
    if (m_interface->characterAt(end) != QLatin1Char(']'))
1015
1016
        return false;

Leandro Melo's avatar
Leandro Melo committed
1017
    QTextCursor tc(m_interface->document());
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
    tc.setPosition(end);
    BackwardsScanner tokens(tc);
    if (tokens[tokens.startToken() - 1].isNot(T_RBRACKET))
        return false;

    const int start = tokens.startOfMatchingBrace(tokens.startToken());
    if (start == tokens.startToken())
        return false;

    const int startPos = tokens[start].begin() + tokens.startPosition();
Leandro Melo's avatar
Leandro Melo committed
1028
    const QString expr = m_interface->textAt(startPos, m_interface->position() - startPos);
1029

1030
    Document::Ptr thisDocument = m_interface->snapshot().document(m_interface->file()->fileName());
1031
1032
1033
    if (! thisDocument)
        return false;

1034
1035
    m_model->m_typeOfExpression->init(thisDocument, m_interface->snapshot());

1036
    int line = 0, column = 0;
Leandro Melo's avatar
Leandro Melo committed
1037
    Convenience::convertPosition(m_interface->document(), m_interface->position(), &line, &column);
1038
1039
1040
1041
    Scope *scope = thisDocument->scopeAt(line, column);
    if (!scope)
        return false;

1042
1043
    const QList<LookupItem> items = (*m_model->m_typeOfExpression)(expr, scope);
    LookupContext lookupContext(thisDocument, m_interface->snapshot());
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064

    foreach (const LookupItem &item, items) {
        FullySpecifiedType ty = item.type().simplified();
        if (ty->isPointerType()) {
            ty = ty->asPointerType()->elementType().simplified();

            if (NamedType *namedTy = ty->asNamedType()) {
                ClassOrNamespace *binding = lookupContext.lookupType(namedTy->name(), item.scope());
                completeObjCMsgSend(binding, false);
            }
        } else {
            if (ObjCClass *clazz = ty->asObjCClassType()) {
                ClassOrNamespace *binding = lookupContext.lookupType(clazz->name(), item.scope());
                completeObjCMsgSend(binding, true);
            }
        }
    }

    if (m_completions.isEmpty())
        return false;

Leandro Melo's avatar
Leandro Melo committed
1065
    m_startPosition = m_interface->position();
1066
1067
1068
    return true;
}

Leandro Melo's avatar
Leandro Melo committed
1069
1070
1071
1072
void CppCompletionAssistProcessor::addCompletionItem(const QString &text,
                                                     const QIcon &icon,
                                                     int order,
                                                     const QVariant &data)
1073
{
Leandro Melo's avatar
Leandro Melo committed
1074
1075
1076
1077
1078
1079
1080
    BasicProposalItem *item = new CppAssistProposalItem;
    item->setText(text);
    item->setIcon(icon);
    item->setOrder(order);
    item->setData(data);
    m_completions.append(item);
}
1081

Leandro Melo's avatar
Leandro Melo committed
1082
1083
1084
1085
1086
1087
1088
void CppCompletionAssistProcessor::addCompletionItem(CPlusPlus::Symbol *symbol)
{
    ConvertToCompletionItem toCompletionItem;
    BasicProposalItem *item = toCompletionItem(symbol);
    if (item) {
        item->setIcon(m_icons.iconForSymbol(symbol));
        m_completions.append(item);
1089
    }
Leandro Melo's avatar
Leandro Melo committed
1090
}
1091

Leandro Melo's avatar
Leandro Melo committed
1092
1093
1094
1095
1096
1097
1098
1099
void CppCompletionAssistProcessor::completeObjCMsgSend(CPlusPlus::ClassOrNamespace *binding,
                                                       bool staticClassAccess)
{
    QList<Scope*> memberScopes;
    foreach (Symbol *s, binding->symbols()) {
        if (ObjCClass *c = s->asObjCClass())
            memberScopes.append(c);
    }
con's avatar
con committed
1100

Leandro Melo's avatar
Leandro Melo committed
1101
1102
1103
    foreach (Scope *scope, memberScopes) {
        for (unsigned i = 0; i < scope->memberCount(); ++i) {
            Symbol *symbol = scope->memberAt(i);
con's avatar
con committed
1104

Leandro Melo's avatar
Leandro Melo committed
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
            if (ObjCMethod *method = symbol->type()->asObjCMethodType()) {
                if (method->isStatic() == staticClassAccess) {
                    Overview oo;
                    const SelectorNameId *selectorName =
                            method->name()->asSelectorNameId();
                    QString text;
                    QString data;
                    if (selectorName->hasArguments()) {
                        for (unsigned i = 0; i < selectorName->nameCount(); ++i) {
                            if (i > 0)
                                text += QLatin1Char(' ');
                            Symbol *arg = method->argumentAt(i);
                            text += selectorName->nameAt(i)->identifier()->chars();
                            text += QLatin1Char(':');
                            text += TextEditor::Snippet::kVariableDelimiter;
                            text += QLatin1Char('(');
                            text += oo(arg->type());
                            text += QLatin1Char(')');
                            text += oo(arg->name());
                            text += TextEditor::Snippet::kVariableDelimiter;
                        }
                    } else {
                        text = selectorName->identifier()->chars();
                    }
                    data = text;
con's avatar
con committed
1130

Leandro Melo's avatar
Leandro Melo committed
1131
1132
1133
1134
                    if (!text.isEmpty())
                        addCompletionItem(text, QIcon(), 0, QVariant::fromValue(data));
                }
            }
1135
1136
        }
    }
Leandro Melo's avatar
Leandro Melo committed
1137
}
1138

Leandro Melo's avatar
Leandro Melo committed
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
bool CppCompletionAssistProcessor::completeInclude(const QTextCursor &cursor)
{
    QString directoryPrefix;
    if (m_model->m_completionOperator == T_SLASH) {
        QTextCursor c = cursor;
        c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
        QString sel = c.selectedText();
        int startCharPos = sel.indexOf(QLatin1Char('"'));
        if (startCharPos == -1) {
            startCharPos = sel.indexOf(QLatin1Char('<'));
            m_model->m_completionOperator = T_ANGLE_STRING_LITERAL;
        } else {
            m_model->m_completionOperator = T_STRING_LITERAL;
        }
        if (startCharPos != -1)
            directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1);
1155
1156
    }

Leandro Melo's avatar
Leandro Melo committed
1157
1158
1159
1160
1161
1162
    // Make completion for all relevant includes
    QStringList includePaths = m_interface->includePaths();
    const QString &currentFilePath = QFileInfo(m_interface->file()->fileName()).path();
    if (!includePaths.contains(currentFilePath))
        includePaths.append(currentFilePath);

1163
1164
1165
1166
    const Core::MimeType mimeType =
            Core::ICore::instance()->mimeDatabase()->findByType(QLatin1String("text/x-c++hdr"));
    const QStringList suffixes = mimeType.suffixes();

Leandro Melo's avatar
Leandro Melo committed
1167
1168
1169
1170
1171
1172
    foreach (const QString &includePath, includePaths) {
        QString realPath = includePath;
        if (!directoryPrefix.isEmpty()) {
            realPath += QLatin1Char('/');
            realPath += directoryPrefix;
        }
1173
        completeInclude(realPath, suffixes);
1174
1175
    }

Leandro Melo's avatar
Leandro Melo committed
1176
1177
1178
1179
1180
1181
    foreach (const QString &frameworkPath, m_interface->frameworkPaths()) {
        QString realPath = frameworkPath;
        if (!directoryPrefix.isEmpty()) {
            realPath += QLatin1Char('/');
            realPath += directoryPrefix;
            realPath += QLatin1String(".framework/Headers");
1182
        }
1183
        completeInclude(realPath, suffixes);
1184
1185
    }

Leandro Melo's avatar
Leandro Melo committed
1186
1187
    return !m_completions.isEmpty();
}
1188

1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
void CppCompletionAssistProcessor::completeInclude(const QString &realPath,
                                                   const QStringList &suffixes)
{
    QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
    while (i.hasNext()) {
        const QString fileName = i.next();
        const QFileInfo fileInfo = i.fileInfo();
        const QString suffix = fileInfo.suffix();
        if (suffix.isEmpty() || suffixes.contains(suffix)) {
            QString text = fileName.mid(realPath.length() + 1);
            if (fileInfo.isDir())
                text += QLatin1Char('/');
            addCompletionItem(text, m_icons.keywordIcon());
        }
    }
}

Leandro Melo's avatar
Leandro Melo committed
1206
1207
1208
1209
void CppCompletionAssistProcessor::completePreprocessor()
{
    foreach (const QString &preprocessorCompletion, preprocessorCompletions)
        addCompletionItem(preprocessorCompletion);
1210

Leandro Melo's avatar
Leandro Melo committed
1211
1212
1213
    if (objcKeywordsWanted())
        addCompletionItem(QLatin1String("import"));
}
1214

Leandro Melo's avatar
Leandro Melo committed
1215
1216
1217
1218
bool CppCompletionAssistProcessor::objcKeywordsWanted() const
{
    if (!m_objcEnabled)
        return false;
1219

Leandro Melo's avatar
Leandro Melo committed
1220
1221
    const Core::IFile *file = m_interface->file();
    QString fileName = file->fileName();
con's avatar
con committed
1222

Leandro Melo's avatar
Leandro Melo committed
1223
1224
    const Core::MimeDatabase *mdb = Core::ICore::instance()->mimeDatabase();
    return mdb->findByFile(fileName).type() == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE;
1225
}
con's avatar
con committed
1226

Leandro Melo's avatar
Leandro Melo committed
1227
1228
1229
1230
int CppCompletionAssistProcessor::startCompletionInternal(const QString fileName,
                                                          unsigned line, unsigned column,
                                                          const QString &expr,
                                                          int endOfExpression)
1231
1232
{
    QString expression = expr.trimmed();
1233

1234
    Document::Ptr thisDocument = m_interface->snapshot().document(fileName);
1235
1236
    if (! thisDocument)
        return -1;
con's avatar
con committed
1237

1238
    m_model->m_typeOfExpression->init(thisDocument, m_interface->snapshot());
1239
1240
1241

    Scope *scope = thisDocument->scopeAt(line, column);
    Q_ASSERT(scope != 0);
con's avatar
con committed
1242

1243
    if (expression.isEmpty()) {
Leandro Melo's avatar
Leandro Melo committed
1244
        if (m_model->m_completionOperator == T_EOF_SYMBOL || m_model->m_completionOperator == T_COLON_COLON) {
1245
            (void) (*m_model->m_typeOfExpression)(expression, scope);
1246
1247
1248
1249
1250
            globalCompletion(scope);
            if (m_completions.isEmpty())
                return -1;
            return m_startPosition;
        }
con's avatar
con committed
1251

Leandro Melo's avatar
Leandro Melo committed
1252
        else if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) {
con's avatar
con committed
1253
1254
1255
            // Apply signal/slot completion on 'this'
            expression = QLatin1String("this");
        }
1256
    }
con's avatar
con committed
1257

1258
1259
    QList<LookupItem> results =
            (*m_model->m_typeOfExpression)(expression, scope, TypeOfExpression::Preprocess);
1260

1261
    if (results.isEmpty()) {
Leandro Melo's avatar
Leandro Melo committed
1262
        if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) {
1263
1264
            if (! (expression.isEmpty() || expression == QLatin1String("this"))) {
                expression = QLatin1String("this");
1265
                results = (*m_model->m_typeOfExpression)(expression, scope);
1266
            }
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
1267

1268
1269
            if (results.isEmpty())
                return -1;
Roberto Raggi's avatar
Cleanup    
Roberto Raggi committed
1270

Leandro Melo's avatar
Leandro Melo committed
1271
        } else if (m_model->m_completionOperator == T_LPAREN) {
1272
1273
            // Find the expression that precedes the current name
            int index = endOfExpression;
Leandro Melo's avatar
Leandro Melo committed
1274
            while (m_interface->characterAt(index - 1).isSpace())
1275
1276
1277
                --index;
            index = findStartOfName(index);

Leandro Melo's avatar
Leandro Melo committed
1278
            QTextCursor tc(m_interface->document());
1279
            tc.setPosition(index);
1280

Erik Verbruggen's avatar
Erik Verbruggen committed
1281
            ExpressionUnderCursor expressionUnderCursor;
1282
            const QString baseExpression = expressionUnderCursor(tc);
1283
1284

            // Resolve the type of this expression
1285
            const QList<LookupItem> results =
1286
                    (*m_model->m_typeOfExpression)(baseExpression, scope,
1287
                                     TypeOfExpression::Preprocess);
1288
1289

            // If it's a class, add completions for the constructors
1290
1291
            foreach (const LookupItem &result, results) {
                if (result.type()->isClassType()) {
1292
                    if (completeConstructorOrFunction(results, endOfExpression, true))
1293
                        return m_startPosition;
1294

1295
1296
1297
                    break;
                }
            }
1298
1299
            return -1;

Leandro Melo's avatar
Leandro Melo committed
1300
1301
1302
        } else {
            // nothing to do.
            return -1;
1303

Leandro Melo's avatar
Leandro Melo committed
1304
1305
        }
    }
1306

Leandro Melo's avatar
Leandro Melo committed
1307
1308
1309
1310
1311
    switch (m_model->m_completionOperator) {
    case T_LPAREN:
        if (completeConstructorOrFunction(results, endOfExpression, false))
            return m_startPosition;
        break;
1312

Leandro Melo's avatar
Leandro Melo committed
1313
1314
1315
1316
1317
    case T_DOT:
    case T_ARROW:
        if (completeMember(results))
            return m_startPosition;
        break;
1318

Leandro Melo's avatar
Leandro Melo committed
1319
1320
1321
1322
    case T_COLON_COLON:
        if (completeScope(results))
            return m_startPosition;
        break;
1323

Leandro Melo's avatar
Leandro Melo committed
1324
1325
1326
1327
    case T_SIGNAL:
        if (completeSignal(results))
            return m_startPosition;
        break;
1328

Leandro Melo's avatar
Leandro Melo committed
1329
1330
1331
1332
    case T_SLOT:
        if (completeSlot(results))
            return m_startPosition;
        break;
1333

Leandro Melo's avatar
Leandro Melo committed
1334
1335
1336
    default:
        break;
    } // end of switch
1337

Leandro Melo's avatar
Leandro Melo committed
1338
1339
1340
    // nothing to do.
    return -1;
}
1341

Leandro Melo's avatar
Leandro Melo committed
1342
1343
void CppCompletionAssistProcessor::globalCompletion(CPlusPlus::Scope *currentScope)
{
1344
    const LookupContext &context = m_model->m_typeOfExpression->context();
1345

Leandro Melo's avatar
Leandro Melo committed
1346
1347
1348
1349
1350
1351
1352
    if (m_model->m_completionOperator == T_COLON_COLON) {
        completeNamespace(context.globalNamespace());
        return;
    }

    QList<ClassOrNamespace *> usingBindings;
    ClassOrNamespace *currentBinding = 0;
1353

Leandro Melo's avatar
Leandro Melo committed
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
    for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) {
        if (scope->isBlock()) {
            if (ClassOrNamespace *binding = context.lookupType(scope)) {
                for (unsigned i = 0; i < scope->memberCount(); ++i) {
                    Symbol *member = scope->memberAt(i);
                    if (! member->name())
                        continue;
                    else if (UsingNamespaceDirective *u = member->asUsingNamespaceDirective()) {
                        if (ClassOrNamespace *b = binding->lookupType(u->name()))
                            usingBindings.append(b);
                    }
1365
1366
                }
            }
Leandro Melo's avatar
Leandro Melo committed
1367
1368
1369
        } else if (scope->isFunction() || scope->isClass() || scope->isNamespace()) {
            currentBinding = context.lookupType(scope);
            break;
1370
        }
1371
    }
1372

Leandro Melo's avatar
Leandro Melo committed
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384