OSDN Git Service

QmlJS: Fix build with Qt 4.7.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qmljseditor / qmljscompletionassist.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 **
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 **
23 ** Other Usage
24 **
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
30 **
31 **************************************************************************/
32
33 #include "qmljscompletionassist.h"
34 #include "qmljseditorconstants.h"
35 #include "qmljsreuse.h"
36 #include "qmlexpressionundercursor.h"
37
38 #include <coreplugin/ifile.h>
39
40 #include <texteditor/codeassist/iassistinterface.h>
41 #include <texteditor/codeassist/genericproposal.h>
42 #include <texteditor/codeassist/functionhintproposal.h>
43 #include <texteditor/codeassist/ifunctionhintproposalmodel.h>
44
45 #include <utils/qtcassert.h>
46
47 #include <qmljs/qmljsmodelmanagerinterface.h>
48 #include <qmljs/parser/qmljsast_p.h>
49 #include <qmljs/qmljsinterpreter.h>
50 #include <qmljs/qmljscontext.h>
51 #include <qmljs/qmljsscopechain.h>
52 #include <qmljs/qmljsscanner.h>
53 #include <qmljs/qmljsbind.h>
54 #include <qmljs/qmljscompletioncontextfinder.h>
55 #include <qmljs/qmljsscopebuilder.h>
56
57 #include <QtCore/QFile>
58 #include <QtCore/QFileInfo>
59 #include <QtCore/QDir>
60 #include <QtCore/QDebug>
61 #include <QtCore/QtAlgorithms>
62 #include <QtCore/QDirIterator>
63 #include <QtCore/QStringList>
64 #include <QtGui/QIcon>
65
66 using namespace QmlJS;
67 using namespace QmlJSEditor;
68 using namespace Internal;
69 using namespace TextEditor;
70
71 namespace {
72
73 enum CompletionOrder {
74     EnumValueOrder = -5,
75     SnippetOrder = -15,
76     PropertyOrder = -10,
77     SymbolOrder = -20,
78     KeywordOrder = -25,
79     TypeOrder = -30
80 };
81
82 static void addCompletion(QList<TextEditor::BasicProposalItem *> *completions,
83                           const QString &text,
84                           const QIcon &icon,
85                           int order,
86                           const QVariant &data = QVariant())
87 {
88     if (text.isEmpty())
89         return;
90
91     BasicProposalItem *item = new QmlJSAssistProposalItem;
92     item->setText(text);
93     item->setIcon(icon);
94     item->setOrder(order);
95     item->setData(data);
96     completions->append(item);
97 }
98
99 static void addCompletions(QList<TextEditor::BasicProposalItem *> *completions,
100                            const QStringList &newCompletions,
101                            const QIcon &icon,
102                            int order)
103 {
104     foreach (const QString &text, newCompletions)
105         addCompletion(completions, text, icon, order);
106 }
107
108 class PropertyProcessor
109 {
110 public:
111     virtual void operator()(const Value *base, const QString &name, const Value *value) = 0;
112 };
113
114 class CompletionAdder : public PropertyProcessor
115 {
116 protected:
117     QList<TextEditor::BasicProposalItem *> *completions;
118
119 public:
120     CompletionAdder(QList<TextEditor::BasicProposalItem *> *completions,
121                     const QIcon &icon, int order)
122         : completions(completions)
123         , icon(icon)
124         , order(order)
125     {}
126
127     virtual void operator()(const Value *base, const QString &name, const Value *value)
128     {
129         Q_UNUSED(base)
130         Q_UNUSED(value)
131         addCompletion(completions, name, icon, order);
132     }
133
134     QIcon icon;
135     int order;
136 };
137
138 class LhsCompletionAdder : public CompletionAdder
139 {
140 public:
141     LhsCompletionAdder(QList<TextEditor::BasicProposalItem *> *completions,
142                        const QIcon &icon,
143                        int order,
144                        bool afterOn)
145         : CompletionAdder(completions, icon, order)
146         , afterOn(afterOn)
147     {}
148
149     virtual void operator ()(const Value *base, const QString &name, const Value *)
150     {
151         const QmlObjectValue *qmlBase = dynamic_cast<const QmlObjectValue *>(base);
152
153         QString itemText = name;
154         QString postfix;
155         if (!itemText.isEmpty() && itemText.at(0).isLower())
156             postfix = QLatin1String(": ");
157         if (afterOn)
158             postfix = QLatin1String(" {");
159
160         // readonly pointer properties (anchors, ...) always get a .
161         if (qmlBase && !qmlBase->isWritable(name) && qmlBase->isPointer(name))
162             postfix = QLatin1Char('.');
163
164         itemText.append(postfix);
165
166         addCompletion(completions, itemText, icon, order);
167     }
168
169     bool afterOn;
170 };
171
172 class ProcessProperties: private MemberProcessor
173 {
174     QSet<const ObjectValue *> _processed;
175     bool _globalCompletion;
176     bool _enumerateGeneratedSlots;
177     bool _enumerateSlots;
178     const ScopeChain *_scopeChain;
179     const ObjectValue *_currentObject;
180     PropertyProcessor *_propertyProcessor;
181
182 public:
183     ProcessProperties(const ScopeChain *scopeChain)
184         : _globalCompletion(false),
185           _enumerateGeneratedSlots(false),
186           _enumerateSlots(true),
187           _scopeChain(scopeChain),
188           _currentObject(0),
189           _propertyProcessor(0)
190     {
191     }
192
193     void setGlobalCompletion(bool globalCompletion)
194     {
195         _globalCompletion = globalCompletion;
196     }
197
198     void setEnumerateGeneratedSlots(bool enumerate)
199     {
200         _enumerateGeneratedSlots = enumerate;
201     }
202
203     void setEnumerateSlots(bool enumerate)
204     {
205         _enumerateSlots = enumerate;
206     }
207
208     void operator ()(const Value *value, PropertyProcessor *processor)
209     {
210         _processed.clear();
211         _propertyProcessor = processor;
212
213         processProperties(value);
214     }
215
216     void operator ()(PropertyProcessor *processor)
217     {
218         _processed.clear();
219         _propertyProcessor = processor;
220
221         foreach (const ObjectValue *scope, _scopeChain->all())
222             processProperties(scope);
223     }
224
225 private:
226     void process(const QString &name, const Value *value)
227     {
228         (*_propertyProcessor)(_currentObject, name, value);
229     }
230
231     virtual bool processProperty(const QString &name, const Value *value)
232     {
233         process(name, value);
234         return true;
235     }
236
237     virtual bool processEnumerator(const QString &name, const Value *value)
238     {
239         if (! _globalCompletion)
240             process(name, value);
241         return true;
242     }
243
244     virtual bool processSignal(const QString &name, const Value *value)
245     {
246         if (_globalCompletion)
247             process(name, value);
248         return true;
249     }
250
251     virtual bool processSlot(const QString &name, const Value *value)
252     {
253         if (_enumerateSlots)
254             process(name, value);
255         return true;
256     }
257
258     virtual bool processGeneratedSlot(const QString &name, const Value *value)
259     {
260         if (_enumerateGeneratedSlots || (_currentObject && _currentObject->className().endsWith(QLatin1String("Keys")))) {
261             // ### FIXME: add support for attached properties.
262             process(name, value);
263         }
264         return true;
265     }
266
267     void processProperties(const Value *value)
268     {
269         if (! value)
270             return;
271         else if (const ObjectValue *object = value->asObjectValue()) {
272             processProperties(object);
273         }
274     }
275
276     void processProperties(const ObjectValue *object)
277     {
278         if (! object || _processed.contains(object))
279             return;
280
281         _processed.insert(object);
282         processProperties(object->prototype(_scopeChain->context()));
283
284         _currentObject = object;
285         object->processMembers(this);
286         _currentObject = 0;
287     }
288 };
289
290 const Value *getPropertyValue(const ObjectValue *object,
291                                            const QStringList &propertyNames,
292                                            const ContextPtr &context)
293 {
294     if (propertyNames.isEmpty() || !object)
295         return 0;
296
297     const Value *value = object;
298     foreach (const QString &name, propertyNames) {
299         if (const ObjectValue *objectValue = value->asObjectValue()) {
300             value = objectValue->lookupMember(name, context);
301             if (!value)
302                 return 0;
303         } else {
304             return 0;
305         }
306     }
307     return value;
308 }
309
310 bool isLiteral(AST::Node *ast)
311 {
312     if (AST::cast<AST::StringLiteral *>(ast))
313         return true;
314     else if (AST::cast<AST::NumericLiteral *>(ast))
315         return true;
316     else
317         return false;
318 }
319
320 } // Anonymous
321
322 // -----------------------
323 // QmlJSAssistProposalItem
324 // -----------------------
325 bool QmlJSAssistProposalItem::prematurelyApplies(const QChar &c) const
326 {
327     if (data().canConvert<QString>()) // snippet
328         return false;
329
330     return (text().endsWith(QLatin1String(": ")) && c == QLatin1Char(':'))
331             || (text().endsWith(QLatin1Char('.')) && c == QLatin1Char('.'));
332 }
333
334 void QmlJSAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor *editor,
335                                                       int basePosition) const
336 {
337     const int currentPosition = editor->position();
338     editor->setCursorPosition(basePosition);
339     editor->remove(currentPosition - basePosition);
340
341     QString replaceable;
342     const QString &content = text();
343     if (content.endsWith(QLatin1String(": ")))
344         replaceable = QLatin1String(": ");
345     else if (content.endsWith(QLatin1Char('.')))
346         replaceable = QLatin1String(".");
347     int replacedLength = 0;
348     for (int i = 0; i < replaceable.length(); ++i) {
349         const QChar a = replaceable.at(i);
350         const QChar b = editor->characterAt(editor->position() + i);
351         if (a == b)
352             ++replacedLength;
353         else
354             break;
355     }
356     const int length = editor->position() - basePosition + replacedLength;
357     editor->replace(length, content);
358 }
359
360 // -------------------------
361 // FunctionHintProposalModel
362 // -------------------------
363 class FunctionHintProposalModel : public TextEditor::IFunctionHintProposalModel
364 {
365 public:
366     FunctionHintProposalModel(const QString &functionName, const QStringList &signature)
367         : m_functionName(functionName)
368         , m_signature(signature)
369         , m_minimumArgumentCount(signature.size())
370     {}
371
372     virtual void reset() {}
373     virtual int size() const { return 1; }
374     virtual QString text(int index) const;
375     virtual int activeArgument(const QString &prefix) const;
376
377 private:
378     QString m_functionName;
379     QStringList m_signature;
380     int m_minimumArgumentCount;
381 };
382
383 QString FunctionHintProposalModel::text(int index) const
384 {
385     Q_UNUSED(index)
386
387     QString prettyMethod;
388     prettyMethod += QString::fromLatin1("function ");
389     prettyMethod += m_functionName;
390     prettyMethod += QLatin1Char('(');
391     for (int i = 0; i < m_minimumArgumentCount; ++i) {
392         if (i != 0)
393             prettyMethod += QLatin1String(", ");
394
395         QString arg = m_signature.at(i);
396         if (arg.isEmpty()) {
397             arg = QLatin1String("arg");
398             arg += QString::number(i + 1);
399         }
400
401         prettyMethod += arg;
402     }
403     prettyMethod += QLatin1Char(')');
404     return prettyMethod;
405 }
406
407 int FunctionHintProposalModel::activeArgument(const QString &prefix) const
408 {
409     int argnr = 0;
410     int parcount = 0;
411     Scanner tokenize;
412     const QList<Token> tokens = tokenize(prefix);
413     for (int i = 0; i < tokens.count(); ++i) {
414         const Token &tk = tokens.at(i);
415         if (tk.is(Token::LeftParenthesis))
416             ++parcount;
417         else if (tk.is(Token::RightParenthesis))
418             --parcount;
419         else if (! parcount && tk.is(Token::Colon))
420             ++argnr;
421     }
422
423     if (parcount < 0)
424         return -1;
425
426     return argnr;
427 }
428
429 // -----------------------------
430 // QmlJSCompletionAssistProvider
431 // -----------------------------
432 bool QmlJSCompletionAssistProvider::supportsEditor(const QString &editorId) const
433 {
434     return editorId == QLatin1String(Constants::C_QMLJSEDITOR_ID);
435 }
436
437 int QmlJSCompletionAssistProvider::activationCharSequenceLength() const
438 {
439     return 1;
440 }
441
442 bool QmlJSCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
443 {
444     return isActivationChar(sequence.at(0));
445 }
446
447 bool QmlJSCompletionAssistProvider::isContinuationChar(const QChar &c) const
448 {
449     return isIdentifierChar(c, false);
450 }
451
452 IAssistProcessor *QmlJSCompletionAssistProvider::createProcessor() const
453 {
454     return new QmlJSCompletionAssistProcessor;
455 }
456
457 // ------------------------------
458 // QmlJSCompletionAssistProcessor
459 // ------------------------------
460 QmlJSCompletionAssistProcessor::QmlJSCompletionAssistProcessor()
461     : m_startPosition(0)
462     , m_snippetCollector(Constants::QML_SNIPPETS_GROUP_ID, iconForColor(Qt::red), SnippetOrder)
463 {}
464
465 QmlJSCompletionAssistProcessor::~QmlJSCompletionAssistProcessor()
466 {}
467
468 IAssistProposal *QmlJSCompletionAssistProcessor::createContentProposal() const
469 {
470     IGenericProposalModel *model = new QmlJSAssistProposalModel(m_completions);
471     IAssistProposal *proposal = new GenericProposal(m_startPosition, model);
472     return proposal;
473 }
474
475 IAssistProposal *QmlJSCompletionAssistProcessor::createHintProposal(const QString &functionName,
476                                                                     const QStringList &signature) const
477 {
478     IFunctionHintProposalModel *model = new FunctionHintProposalModel(functionName, signature);
479     IAssistProposal *proposal = new FunctionHintProposal(m_startPosition, model);
480     return proposal;
481 }
482
483 IAssistProposal *QmlJSCompletionAssistProcessor::perform(const IAssistInterface *assistInterface)
484 {
485     m_interface.reset(static_cast<const QmlJSCompletionAssistInterface *>(assistInterface));
486
487     if (assistInterface->reason() == IdleEditor && !acceptsIdleEditor())
488         return 0;
489
490     const QString &fileName = m_interface->file()->fileName();
491
492     m_startPosition = assistInterface->position();
493     while (isIdentifierChar(m_interface->document()->characterAt(m_startPosition - 1), false, false))
494         --m_startPosition;
495     const bool onIdentifier = m_startPosition != assistInterface->position();
496
497     m_completions.clear();
498
499     const QmlJSCompletionAssistInterface *qmlInterface =
500             static_cast<const QmlJSCompletionAssistInterface *>(assistInterface);
501     const SemanticInfo &semanticInfo = qmlInterface->semanticInfo();
502     if (!semanticInfo.isValid())
503         return 0;
504
505     const Document::Ptr document = semanticInfo.document;
506     const QFileInfo currentFileInfo(fileName);
507
508     bool isQmlFile = false;
509     if (currentFileInfo.suffix() == QLatin1String("qml"))
510         isQmlFile = true;
511
512     const QList<AST::Node *> path = semanticInfo.rangePath(m_interface->position());
513     const ContextPtr &context = semanticInfo.context;
514     const ScopeChain &scopeChain = semanticInfo.scopeChain(path);
515
516     // The completionOperator is the character under the cursor or directly before the
517     // identifier under cursor. Use in conjunction with onIdentifier. Examples:
518     // a + b<complete> -> ' '
519     // a +<complete> -> '+'
520     // a +b<complete> -> '+'
521     QChar completionOperator;
522     if (m_startPosition > 0)
523         completionOperator = m_interface->document()->characterAt(m_startPosition - 1);
524
525     QTextCursor startPositionCursor(qmlInterface->document());
526     startPositionCursor.setPosition(m_startPosition);
527     CompletionContextFinder contextFinder(startPositionCursor);
528
529     const ObjectValue *qmlScopeType = 0;
530     if (contextFinder.isInQmlContext()) {
531         // find the enclosing qml object
532         // ### this should use semanticInfo.declaringMember instead, but that may also return functions
533         int i;
534         for (i = path.size() - 1; i >= 0; --i) {
535             AST::Node *node = path[i];
536             if (AST::cast<AST::UiObjectDefinition *>(node) || AST::cast<AST::UiObjectBinding *>(node)) {
537                 qmlScopeType = document->bind()->findQmlObject(node);
538                 if (qmlScopeType)
539                     break;
540             }
541         }
542         // grouped property bindings change the scope type
543         for (i++; i < path.size(); ++i) {
544             AST::UiObjectDefinition *objDef = AST::cast<AST::UiObjectDefinition *>(path[i]);
545             if (!objDef || !document->bind()->isGroupedPropertyBinding(objDef))
546                 break;
547             const ObjectValue *newScopeType = qmlScopeType;
548             for (AST::UiQualifiedId *it = objDef->qualifiedTypeNameId; it; it = it->next) {
549                 if (!newScopeType || it->name.isEmpty()) {
550                     newScopeType = 0;
551                     break;
552                 }
553                 const Value *v = newScopeType->lookupMember(it->name.toString(), context);
554                 v = context->lookupReference(v);
555                 newScopeType = value_cast<const ObjectValue *>(v);
556             }
557             if (!newScopeType)
558                 break;
559             qmlScopeType = newScopeType;
560         }
561         // fallback to getting the base type object
562         if (!qmlScopeType)
563             qmlScopeType = context->lookupType(document.data(), contextFinder.qmlObjectTypeName());
564     }
565
566     if (contextFinder.isInStringLiteral()) {
567         // get the text of the literal up to the cursor position
568         //QTextCursor tc = textWidget->textCursor();
569         QTextCursor tc(qmlInterface->document());
570         tc.setPosition(qmlInterface->position());
571         QmlExpressionUnderCursor expressionUnderCursor;
572         expressionUnderCursor(tc);
573         QString literalText = expressionUnderCursor.text();
574         QTC_ASSERT(!literalText.isEmpty() && (
575                        literalText.at(0) == QLatin1Char('"')
576                        || literalText.at(0) == QLatin1Char('\'')), return 0);
577         literalText = literalText.mid(1);
578
579         if (contextFinder.isInImport()) {
580             QStringList patterns;
581             patterns << QLatin1String("*.qml") << QLatin1String("*.js");
582             if (completeFileName(document->path(), literalText, patterns))
583                 return createContentProposal();
584             return 0;
585         }
586
587         const Value *value =
588                 getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context);
589         if (!value) {
590             // do nothing
591         } else if (value->asUrlValue()) {
592             if (completeUrl(document->path(), literalText))
593                 return createContentProposal();
594         }
595
596         // ### enum completion?
597
598         return 0;
599     }
600     // member "a.bc<complete>" or function "foo(<complete>" completion
601     else if (completionOperator == QLatin1Char('.')
602              || (completionOperator == QLatin1Char('(') && !onIdentifier)) {
603         // Look at the expression under cursor.
604         //QTextCursor tc = textWidget->textCursor();
605         QTextCursor tc(qmlInterface->document());
606         tc.setPosition(m_startPosition - 1);
607
608         QmlExpressionUnderCursor expressionUnderCursor;
609         QmlJS::AST::ExpressionNode *expression = expressionUnderCursor(tc);
610
611         if (expression != 0 && ! isLiteral(expression)) {
612             // Evaluate the expression under cursor.
613             ValueOwner *interp = context->valueOwner();
614             const Value *value =
615                     interp->convertToObject(scopeChain.evaluate(expression));
616             //qDebug() << "type:" << interp->typeId(value);
617
618             if (value && completionOperator == QLatin1Char('.')) { // member completion
619                 ProcessProperties processProperties(&scopeChain);
620                 if (contextFinder.isInLhsOfBinding() && qmlScopeType) {
621                     LhsCompletionAdder completionAdder(&m_completions, m_interface->symbolIcon(),
622                                                        PropertyOrder, contextFinder.isAfterOnInLhsOfBinding());
623                     processProperties.setEnumerateGeneratedSlots(true);
624                     processProperties(value, &completionAdder);
625                 } else {
626                     CompletionAdder completionAdder(&m_completions, m_interface->symbolIcon(), SymbolOrder);
627                     processProperties(value, &completionAdder);
628                 }
629             } else if (value
630                        && completionOperator == QLatin1Char('(')
631                        && m_startPosition == m_interface->position()) {
632                 // function completion
633                 if (const FunctionValue *f = value->asFunctionValue()) {
634                     QString functionName = expressionUnderCursor.text();
635                     int indexOfDot = functionName.lastIndexOf(QLatin1Char('.'));
636                     if (indexOfDot != -1)
637                         functionName = functionName.mid(indexOfDot + 1);
638
639                     QStringList signature;
640                     for (int i = 0; i < f->argumentCount(); ++i)
641                         signature.append(f->argumentName(i));
642
643                     return createHintProposal(functionName.trimmed(), signature);
644                 }
645             }
646         }
647
648         if (! m_completions.isEmpty())
649             return createContentProposal();
650         return 0;
651     }
652     // global completion
653     else if (onIdentifier || assistInterface->reason() == ExplicitlyInvoked) {
654
655         bool doGlobalCompletion = true;
656         bool doQmlKeywordCompletion = true;
657         bool doJsKeywordCompletion = true;
658         bool doQmlTypeCompletion = false;
659
660         if (contextFinder.isInLhsOfBinding() && qmlScopeType) {
661             doGlobalCompletion = false;
662             doJsKeywordCompletion = false;
663             doQmlTypeCompletion = true;
664
665             ProcessProperties processProperties(&scopeChain);
666             processProperties.setGlobalCompletion(true);
667             processProperties.setEnumerateGeneratedSlots(true);
668             processProperties.setEnumerateSlots(false);
669
670             // id: is special
671             BasicProposalItem *idProposalItem = new QmlJSAssistProposalItem;
672             idProposalItem->setText(QLatin1String("id: "));
673             idProposalItem->setIcon(m_interface->symbolIcon());
674             idProposalItem->setOrder(PropertyOrder);
675             m_completions.append(idProposalItem);
676
677             {
678                 LhsCompletionAdder completionAdder(&m_completions, m_interface->symbolIcon(),
679                                                    PropertyOrder, contextFinder.isAfterOnInLhsOfBinding());
680                 processProperties(qmlScopeType, &completionAdder);
681             }
682
683             if (ScopeBuilder::isPropertyChangesObject(context, qmlScopeType)
684                     && scopeChain.qmlScopeObjects().size() == 2) {
685                 CompletionAdder completionAdder(&m_completions, m_interface->symbolIcon(), SymbolOrder);
686                 processProperties(scopeChain.qmlScopeObjects().first(), &completionAdder);
687             }
688         }
689
690         if (contextFinder.isInRhsOfBinding() && qmlScopeType) {
691             doQmlKeywordCompletion = false;
692
693             // complete enum values for enum properties
694             const Value *value =
695                     getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context);
696             if (const QmlEnumValue *enumValue =
697                     dynamic_cast<const QmlEnumValue *>(value)) {
698                 const QString &name = context->imports(document.data())->nameForImportedObject(enumValue->owner(), context.data());
699                 foreach (const QString &key, enumValue->keys()) {
700                     QString completion;
701                     if (name.isEmpty())
702                         completion = QString("\"%1\"").arg(key);
703                     else
704                         completion = QString("%1.%2").arg(name, key);
705                     addCompletion(&m_completions, key, m_interface->symbolIcon(),
706                                   EnumValueOrder, completion);
707                 }
708             }
709         }
710
711         if (!contextFinder.isInImport() && !contextFinder.isInQmlContext())
712             doQmlTypeCompletion = true;
713
714         if (doQmlTypeCompletion) {
715             if (const ObjectValue *qmlTypes = scopeChain.qmlTypes()) {
716                 ProcessProperties processProperties(&scopeChain);
717                 CompletionAdder completionAdder(&m_completions, m_interface->symbolIcon(), TypeOrder);
718                 processProperties(qmlTypes, &completionAdder);
719             }
720         }
721
722         if (doGlobalCompletion) {
723             // It's a global completion.
724             ProcessProperties processProperties(&scopeChain);
725             processProperties.setGlobalCompletion(true);
726             CompletionAdder completionAdder(&m_completions, m_interface->symbolIcon(), SymbolOrder);
727             processProperties(&completionAdder);
728         }
729
730         if (doJsKeywordCompletion) {
731             // add js keywords
732             addCompletions(&m_completions, Scanner::keywords(), m_interface->keywordIcon(), KeywordOrder);
733         }
734
735         // add qml extra words
736         if (doQmlKeywordCompletion && isQmlFile) {
737             static QStringList qmlWords;
738             static QStringList qmlWordsAlsoInJs;
739
740             if (qmlWords.isEmpty()) {
741                 qmlWords << QLatin1String("property")
742                             //<< QLatin1String("readonly")
743                          << QLatin1String("signal")
744                          << QLatin1String("import");
745             }
746             if (qmlWordsAlsoInJs.isEmpty())
747                 qmlWordsAlsoInJs << QLatin1String("default") << QLatin1String("function");
748
749             addCompletions(&m_completions, qmlWords, m_interface->keywordIcon(), KeywordOrder);
750             if (!doJsKeywordCompletion)
751                 addCompletions(&m_completions, qmlWordsAlsoInJs, m_interface->keywordIcon(), KeywordOrder);
752         }
753
754         m_completions.append(m_snippetCollector.collect());
755
756         if (! m_completions.isEmpty())
757             return createContentProposal();
758         return 0;
759     }
760
761     return 0;
762 }
763
764 bool QmlJSCompletionAssistProcessor::acceptsIdleEditor() const
765 {
766     const int cursorPos = m_interface->position();
767
768     bool maybeAccept = false;
769     const QChar &charBeforeCursor = m_interface->document()->characterAt(cursorPos - 1);
770     if (isActivationChar(charBeforeCursor)) {
771         maybeAccept = true;
772     } else {
773         const QChar &charUnderCursor = m_interface->document()->characterAt(cursorPos);
774         if (isIdentifierChar(charBeforeCursor)
775                 && ((charUnderCursor.isSpace()
776                     || charUnderCursor.isNull()
777                     || isDelimiterChar(charUnderCursor))
778                 || isIdentifierChar(charUnderCursor))) {
779
780             int startPos = cursorPos - 1;
781             for (; startPos != -1; --startPos) {
782                 if (!isIdentifierChar(m_interface->document()->characterAt(startPos)))
783                     break;
784             }
785             ++startPos;
786
787             const QString &word = m_interface->textAt(startPos, cursorPos - startPos);
788             if (word.length() > 2 && isIdentifierChar(word.at(0), true)) {
789                 for (int i = 1; i < word.length(); ++i) {
790                     if (!isIdentifierChar(word.at(i)))
791                         return false;
792                 }
793                 maybeAccept = true;
794             }
795         }
796     }
797
798     if (maybeAccept) {
799         QTextCursor tc(m_interface->document());
800         tc.setPosition(m_interface->position());
801         const QTextBlock &block = tc.block();
802         const QString &blockText = block.text();
803         const int blockState = qMax(0, block.previous().userState()) & 0xff;
804
805         Scanner scanner;
806         const QList<Token> tokens = scanner(blockText, blockState);
807         const int column = block.position() - m_interface->position();
808         foreach (const Token &tk, tokens) {
809             if (column >= tk.begin() && column <= tk.end()) {
810                 if (charBeforeCursor == QLatin1Char('/') && tk.is(Token::String))
811                     return true; // path completion inside string literals
812                 if (tk.is(Token::Comment) || tk.is(Token::String) || tk.is(Token::RegExp))
813                     return false;
814                 break;
815             }
816         }
817         if (charBeforeCursor != QLatin1Char('/'))
818             return true;
819     }
820
821     return false;
822 }
823
824 bool QmlJSCompletionAssistProcessor::completeFileName(const QString &relativeBasePath,
825                                                       const QString &fileName,
826                                                       const QStringList &patterns)
827 {
828     const QFileInfo fileInfo(fileName);
829     QString directoryPrefix;
830     if (fileInfo.isRelative()) {
831         directoryPrefix = relativeBasePath;
832         directoryPrefix += QDir::separator();
833         directoryPrefix += fileInfo.path();
834     } else {
835         directoryPrefix = fileInfo.path();
836     }
837     if (!QFileInfo(directoryPrefix).exists())
838         return false;
839
840     QDirIterator dirIterator(directoryPrefix,
841                              patterns,
842                              QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
843     while (dirIterator.hasNext()) {
844         dirIterator.next();
845         const QString fileName = dirIterator.fileName();
846
847         BasicProposalItem *item = new QmlJSAssistProposalItem;
848         item->setText(fileName);
849         item->setIcon(m_interface->fileNameIcon());
850         m_completions.append(item);
851     }
852
853     return !m_completions.isEmpty();
854 }
855
856 bool QmlJSCompletionAssistProcessor::completeUrl(const QString &relativeBasePath, const QString &urlString)
857 {
858     const QUrl url(urlString);
859     QString fileName;
860     if (url.scheme().compare(QLatin1String("file"), Qt::CaseInsensitive) == 0) {
861         fileName = url.toLocalFile();
862         // should not trigger completion on 'file://'
863         if (fileName.isEmpty())
864             return false;
865     } else if (url.scheme().isEmpty()) {
866         // don't trigger completion while typing a scheme
867         if (urlString.endsWith(QLatin1String(":/")))
868             return false;
869         fileName = urlString;
870     } else {
871         return false;
872     }
873
874     return completeFileName(relativeBasePath, fileName);
875 }
876
877 // ------------------------------
878 // QmlJSCompletionAssistInterface
879 // ------------------------------
880 QmlJSCompletionAssistInterface::QmlJSCompletionAssistInterface(QTextDocument *document,
881                                                                int position,
882                                                                Core::IFile *file,
883                                                                TextEditor::AssistReason reason,
884                                                                const SemanticInfo &info)
885     : DefaultAssistInterface(document, position, file, reason)
886     , m_semanticInfo(info)
887     , m_darkBlueIcon(iconForColor(Qt::darkBlue))
888     , m_darkYellowIcon(iconForColor(Qt::darkYellow))
889     , m_darkCyanIcon(iconForColor(Qt::darkCyan))
890 {}
891
892 const SemanticInfo &QmlJSCompletionAssistInterface::semanticInfo() const
893 {
894     return m_semanticInfo;
895 }
896
897 namespace {
898
899 struct QmlJSLessThan
900 {
901     bool operator() (const BasicProposalItem *a, const BasicProposalItem *b)
902     {
903         if (a->order() != b->order())
904             return a->order() > b->order();
905         else if (a->text().isEmpty())
906             return true;
907         else if (b->text().isEmpty())
908             return false;
909         else if (a->data().isValid() != b->data().isValid())
910             return a->data().isValid();
911         else if (a->text().at(0).isUpper() && b->text().at(0).isLower())
912             return false;
913         else if (a->text().at(0).isLower() && b->text().at(0).isUpper())
914             return true;
915         return a->text() < b->text();
916     }
917 };
918
919 } // Anonymous
920
921 // -------------------------
922 // QmlJSAssistProposalModel
923 // -------------------------
924 void QmlJSAssistProposalModel::sort()
925 {
926     qSort(currentItems().first, currentItems().second, QmlJSLessThan());
927 }
928
929 bool QmlJSAssistProposalModel::keepPerfectMatch(TextEditor::AssistReason reason) const
930 {
931     return reason == ExplicitlyInvoked;
932 }