OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qmljseditor / qmljscodecompletion.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 qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "qmljscodecompletion.h"
34 #include "qmlexpressionundercursor.h"
35 #include "qmljseditor.h"
36 #include "qmljseditorconstants.h"
37
38 #include <qmljs/qmljsmodelmanagerinterface.h>
39 #include <qmljs/parser/qmljsast_p.h>
40 #include <qmljs/qmljsinterpreter.h>
41 #include <qmljs/qmljslookupcontext.h>
42 #include <qmljs/qmljsscanner.h>
43 #include <qmljs/qmljsbind.h>
44 #include <qmljs/qmljscompletioncontextfinder.h>
45 #include <qmljs/qmljsscopebuilder.h>
46
47 #include <texteditor/basetexteditor.h>
48 #include <texteditor/completionsettings.h>
49
50 #include <coreplugin/icore.h>
51 #include <coreplugin/editormanager/editormanager.h>
52
53 #include <utils/faketooltip.h>
54 #include <utils/qtcassert.h>
55
56 #include <QtCore/QFile>
57 #include <QtCore/QFileInfo>
58 #include <QtCore/QDir>
59 #include <QtCore/QDebug>
60 #include <QtCore/QDirIterator>
61
62 #include <QtGui/QApplication>
63 #include <QtGui/QDesktopWidget>
64 #include <QtGui/QHBoxLayout>
65 #include <QtGui/QLabel>
66 #include <QtGui/QPainter>
67 #include <QtGui/QStyle>
68 #include <QtGui/QTextBlock>
69 #include <QtGui/QToolButton>
70
71 using namespace QmlJSEditor;
72 using namespace QmlJSEditor::Internal;
73 using namespace QmlJS;
74
75 namespace {
76
77 enum CompletionOrder {
78     EnumValueOrder = -5,
79     SnippetOrder = -15,
80     PropertyOrder = -10,
81     SymbolOrder = -20,
82     KeywordOrder = -25,
83     TypeOrder = -30
84 };
85
86 // Temporary workaround until we have proper icons for QML completion items
87 static QIcon iconForColor(const QColor &color)
88 {
89     QPixmap pix(6, 6);
90
91     int pixSize = 20;
92     QBrush br(color);
93
94     QPixmap pm(2 * pixSize, 2 * pixSize);
95     QPainter pmp(&pm);
96     pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray);
97     pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray);
98     pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray);
99     pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray);
100     pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, color);
101     br = QBrush(pm);
102
103     QPainter p(&pix);
104     int corr = 1;
105     QRect r = pix.rect().adjusted(corr, corr, -corr, -corr);
106     p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr);
107     p.fillRect(r, br);
108
109     p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr,
110                r.width() / 2, r.height() / 2,
111                QColor(color.rgb()));
112     p.drawRect(pix.rect().adjusted(0, 0, -1, -1));
113
114     return pix;
115 }
116
117 static bool checkStartOfIdentifier(const QString &word)
118 {
119     if (word.isEmpty())
120         return false;
121
122     const QChar ch = word.at(0);
123
124     switch (ch.unicode()) {
125     case '_': case '$':
126         return true;
127
128     default:
129         return ch.isLetter();
130     }
131 }
132
133 static bool isIdentifierChar(QChar ch)
134 {
135     switch (ch.unicode()) {
136     case '_': case '$':
137         return true;
138
139     default:
140         return ch.isLetterOrNumber();
141     }
142 }
143
144 class SearchPropertyDefinitions: protected AST::Visitor
145 {
146     QList<AST::UiPublicMember *> _properties;
147
148 public:
149     QList<AST::UiPublicMember *> operator()(Document::Ptr doc)
150     {
151         _properties.clear();
152         if (doc && doc->qmlProgram())
153             doc->qmlProgram()->accept(this);
154         return _properties;
155     }
156
157
158 protected:
159     using AST::Visitor::visit;
160
161     virtual bool visit(AST::UiPublicMember *member)
162     {
163         if (member->propertyToken.isValid()) {
164             _properties.append(member);
165         }
166
167         return true;
168     }
169 };
170
171 class EnumerateProperties: private Interpreter::MemberProcessor
172 {
173     QSet<const Interpreter::ObjectValue *> _processed;
174     QHash<QString, const Interpreter::Value *> _properties;
175     bool _globalCompletion;
176     bool _enumerateGeneratedSlots;
177     const Interpreter::Context *_context;
178     const Interpreter::ObjectValue *_currentObject;
179
180 public:
181     EnumerateProperties(const Interpreter::Context *context)
182         : _globalCompletion(false),
183           _enumerateGeneratedSlots(false),
184           _context(context),
185           _currentObject(0)
186     {
187     }
188
189     void setGlobalCompletion(bool globalCompletion)
190     {
191         _globalCompletion = globalCompletion;
192     }
193
194     void setEnumerateGeneratedSlots(bool enumerate)
195     {
196         _enumerateGeneratedSlots = enumerate;
197     }
198
199     QHash<QString, const Interpreter::Value *> operator ()(const Interpreter::Value *value)
200     {
201         _processed.clear();
202         _properties.clear();
203         _currentObject = Interpreter::value_cast<const Interpreter::ObjectValue *>(value);
204
205         enumerateProperties(value);
206
207         return _properties;
208     }
209
210     QHash<QString, const Interpreter::Value *> operator ()()
211     {
212         _processed.clear();
213         _properties.clear();
214         _currentObject = 0;
215
216         foreach (const Interpreter::ObjectValue *scope, _context->scopeChain().all())
217             enumerateProperties(scope);
218
219         return _properties;
220     }
221
222 private:
223     void insertProperty(const QString &name, const Interpreter::Value *value)
224     {
225         _properties.insert(name, value);
226     }
227
228     virtual bool processProperty(const QString &name, const Interpreter::Value *value)
229     {
230         insertProperty(name, value);
231         return true;
232     }
233
234     virtual bool processEnumerator(const QString &name, const Interpreter::Value *value)
235     {
236         if (! _globalCompletion)
237             insertProperty(name, value);
238         return true;
239     }
240
241     virtual bool processSignal(const QString &, const Interpreter::Value *)
242     {
243         return true;
244     }
245
246     virtual bool processSlot(const QString &name, const Interpreter::Value *value)
247     {
248         insertProperty(name, value);
249         return true;
250     }
251
252     virtual bool processGeneratedSlot(const QString &name, const Interpreter::Value *value)
253     {
254         if (_enumerateGeneratedSlots || (_currentObject && _currentObject->className().endsWith(QLatin1String("Keys")))) {
255             // ### FIXME: add support for attached properties.
256             insertProperty(name, value);
257         }
258         return true;
259     }
260
261     void enumerateProperties(const Interpreter::Value *value)
262     {
263         if (! value)
264             return;
265         else if (const Interpreter::ObjectValue *object = value->asObjectValue()) {
266             enumerateProperties(object);
267         }
268     }
269
270     void enumerateProperties(const Interpreter::ObjectValue *object)
271     {
272         if (! object || _processed.contains(object))
273             return;
274
275         _processed.insert(object);
276         enumerateProperties(object->prototype(_context));
277
278         object->processMembers(this);
279     }
280 };
281
282 } // end of anonymous namespace
283
284 namespace QmlJSEditor {
285 namespace Internal {
286
287 class FunctionArgumentWidget : public QLabel
288 {
289 public:
290     FunctionArgumentWidget();
291     void showFunctionHint(const QString &functionName,
292                           const QStringList &signature,
293                           int startPosition);
294
295 protected:
296     bool eventFilter(QObject *obj, QEvent *e);
297
298 private:
299     void updateArgumentHighlight();
300     void updateHintText();
301
302     QString m_functionName;
303     QStringList m_signature;
304     int m_minimumArgumentCount;
305     int m_startpos;
306     int m_currentarg;
307     int m_current;
308     bool m_escapePressed;
309
310     TextEditor::ITextEditor *m_editor;
311
312     QWidget *m_pager;
313     QLabel *m_numberLabel;
314     Utils::FakeToolTip *m_popupFrame;
315 };
316
317
318 FunctionArgumentWidget::FunctionArgumentWidget():
319     m_minimumArgumentCount(0),
320     m_startpos(-1),
321     m_current(0),
322     m_escapePressed(false)
323 {
324     QObject *editorObject = Core::EditorManager::instance()->currentEditor();
325     m_editor = qobject_cast<TextEditor::ITextEditor *>(editorObject);
326
327     m_popupFrame = new Utils::FakeToolTip(m_editor->widget());
328
329     setParent(m_popupFrame);
330     setFocusPolicy(Qt::NoFocus);
331
332     m_pager = new QWidget;
333     QHBoxLayout *hbox = new QHBoxLayout(m_pager);
334     hbox->setMargin(0);
335     hbox->setSpacing(0);
336     m_numberLabel = new QLabel;
337     hbox->addWidget(m_numberLabel);
338
339     QHBoxLayout *layout = new QHBoxLayout;
340     layout->setMargin(0);
341     layout->setSpacing(0);
342     layout->addWidget(m_pager);
343     layout->addWidget(this);
344     m_popupFrame->setLayout(layout);
345
346     setTextFormat(Qt::RichText);
347
348     qApp->installEventFilter(this);
349 }
350
351 void FunctionArgumentWidget::showFunctionHint(const QString &functionName, const QStringList &signature, int startPosition)
352 {
353     if (m_startpos == startPosition)
354         return;
355
356     m_functionName = functionName;
357     m_signature = signature;
358     m_minimumArgumentCount = signature.size();
359     m_startpos = startPosition;
360     m_current = 0;
361     m_escapePressed = false;
362
363     // update the text
364     m_currentarg = -1;
365     updateArgumentHighlight();
366
367     m_popupFrame->show();
368 }
369
370 void FunctionArgumentWidget::updateArgumentHighlight()
371 {
372     int curpos = m_editor->position();
373     if (curpos < m_startpos) {
374         m_popupFrame->close();
375         return;
376     }
377
378     updateHintText();
379
380     QString str = m_editor->textAt(m_startpos, curpos - m_startpos);
381     int argnr = 0;
382     int parcount = 0;
383     Scanner tokenize;
384     const QList<Token> tokens = tokenize(str);
385     for (int i = 0; i < tokens.count(); ++i) {
386         const Token &tk = tokens.at(i);
387         if (tk.is(Token::LeftParenthesis))
388             ++parcount;
389         else if (tk.is(Token::RightParenthesis))
390             --parcount;
391         else if (! parcount && tk.is(Token::Colon))
392             ++argnr;
393     }
394
395     if (m_currentarg != argnr) {
396         // m_currentarg = argnr;
397         updateHintText();
398     }
399
400     if (parcount < 0)
401         m_popupFrame->close();
402 }
403
404 bool FunctionArgumentWidget::eventFilter(QObject *obj, QEvent *e)
405 {
406     switch (e->type()) {
407     case QEvent::ShortcutOverride:
408         if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
409             m_escapePressed = true;
410         }
411         break;
412     case QEvent::KeyPress:
413         if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
414             m_escapePressed = true;
415         }
416         break;
417     case QEvent::KeyRelease:
418         if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_escapePressed) {
419             m_popupFrame->close();
420             return false;
421         }
422         updateArgumentHighlight();
423         break;
424     case QEvent::WindowDeactivate:
425     case QEvent::FocusOut:
426         if (obj != m_editor->widget())
427             break;
428         m_popupFrame->close();
429         break;
430     case QEvent::MouseButtonPress:
431     case QEvent::MouseButtonRelease:
432     case QEvent::MouseButtonDblClick:
433     case QEvent::Wheel: {
434             QWidget *widget = qobject_cast<QWidget *>(obj);
435             if (! (widget == this || m_popupFrame->isAncestorOf(widget))) {
436                 m_popupFrame->close();
437             }
438         }
439         break;
440     default:
441         break;
442     }
443     return false;
444 }
445
446 void FunctionArgumentWidget::updateHintText()
447 {
448     QString prettyMethod;
449     prettyMethod += QString::fromLatin1("function ");
450     prettyMethod += m_functionName;
451     prettyMethod += QLatin1Char('(');
452     for (int i = 0; i < m_minimumArgumentCount; ++i) {
453         if (i != 0)
454             prettyMethod += QLatin1String(", ");
455
456         QString arg = m_signature.at(i);
457         if (arg.isEmpty()) {
458             arg = QLatin1String("arg");
459             arg += QString::number(i + 1);
460         }
461
462         prettyMethod += arg;
463     }
464     prettyMethod += QLatin1Char(')');
465
466     m_numberLabel->setText(prettyMethod);
467
468     m_popupFrame->setFixedWidth(m_popupFrame->minimumSizeHint().width());
469
470     const QDesktopWidget *desktop = QApplication::desktop();
471 #ifdef Q_WS_MAC
472     const QRect screen = desktop->availableGeometry(desktop->screenNumber(m_editor->widget()));
473 #else
474     const QRect screen = desktop->screenGeometry(desktop->screenNumber(m_editor->widget()));
475 #endif
476
477     const QSize sz = m_popupFrame->sizeHint();
478     QPoint pos = m_editor->cursorRect(m_startpos).topLeft();
479     pos.setY(pos.y() - sz.height() - 1);
480
481     if (pos.x() + sz.width() > screen.right())
482         pos.setX(screen.right() - sz.width());
483
484     m_popupFrame->move(pos);
485 }
486
487 } } // namespace QmlJSEditor::Internal
488
489 CodeCompletion::CodeCompletion(ModelManagerInterface *modelManager, QObject *parent)
490     : TextEditor::ICompletionCollector(parent),
491       m_modelManager(modelManager),
492       m_editor(0),
493       m_startPosition(0),
494       m_restartCompletion(false),
495       m_snippetProvider(Constants::QML_SNIPPETS_GROUP_ID, iconForColor(Qt::red), SnippetOrder)
496 {
497     Q_ASSERT(modelManager);
498 }
499
500 CodeCompletion::~CodeCompletion()
501 { }
502
503 TextEditor::ITextEditor *CodeCompletion::editor() const
504 { return m_editor; }
505
506 int CodeCompletion::startPosition() const
507 { return m_startPosition; }
508
509 bool CodeCompletion::shouldRestartCompletion()
510 { return m_restartCompletion; }
511
512 bool CodeCompletion::supportsEditor(TextEditor::ITextEditor *editor) const
513 {
514     if (qobject_cast<QmlJSTextEditorWidget *>(editor->widget()))
515         return true;
516
517     return false;
518 }
519
520 bool CodeCompletion::supportsPolicy(TextEditor::CompletionPolicy policy) const
521 {
522     return policy == TextEditor::SemanticCompletion;
523 }
524
525 bool CodeCompletion::triggersCompletion(TextEditor::ITextEditor *editor)
526 {
527     if (maybeTriggersCompletion(editor)) {
528         // check the token under cursor
529
530         if (QmlJSTextEditorWidget *ed = qobject_cast<QmlJSTextEditorWidget *>(editor->widget())) {
531
532             QTextCursor tc = ed->textCursor();
533             QTextBlock block = tc.block();
534             const int column = tc.positionInBlock();
535             const QChar ch = block.text().at(column - 1);
536             const int blockState = qMax(0, block.previous().userState()) & 0xff;
537             const QString blockText = block.text();
538
539             Scanner scanner;
540             const QList<Token> tokens = scanner(blockText, blockState);
541             foreach (const Token &tk, tokens) {
542                 if (column >= tk.begin() && column <= tk.end()) {
543                     if (ch == QLatin1Char('/') && tk.is(Token::String))
544                         return true; // path completion inside string literals
545                     if (tk.is(Token::Comment) || tk.is(Token::String))
546                         return false;
547                     break;
548                 }
549             }
550             if (ch == QLatin1Char('/'))
551                 return false;
552         }
553         return true;
554     }
555
556     return false;
557 }
558
559 bool CodeCompletion::maybeTriggersCompletion(TextEditor::ITextEditor *editor)
560 {
561     const int cursorPosition = editor->position();
562     const QChar ch = editor->characterAt(cursorPosition - 1);
563
564     if (ch == QLatin1Char('(') || ch == QLatin1Char('.') || ch == QLatin1Char('/'))
565         return true;
566     if (completionSettings().m_completionTrigger != TextEditor::AutomaticCompletion)
567         return false;
568
569     const QChar characterUnderCursor = editor->characterAt(cursorPosition);
570
571     if (isIdentifierChar(ch) && (characterUnderCursor.isSpace() ||
572                                       characterUnderCursor.isNull() ||
573                                       isDelimiter(characterUnderCursor))) {
574         int pos = editor->position() - 1;
575         for (; pos != -1; --pos) {
576             if (! isIdentifierChar(editor->characterAt(pos)))
577                 break;
578         }
579         ++pos;
580
581         const QString word = editor->textAt(pos, cursorPosition - pos);
582         if (word.length() > 2 && checkStartOfIdentifier(word)) {
583             for (int i = 0; i < word.length(); ++i) {
584                 if (! isIdentifierChar(word.at(i)))
585                     return false;
586             }
587             return true;
588         }
589     }
590
591     return false;
592 }
593
594 bool CodeCompletion::isDelimiter(QChar ch) const
595 {
596     switch (ch.unicode()) {
597     case '{':
598     case '}':
599     case '[':
600     case ']':
601     case ')':
602     case '?':
603     case '!':
604     case ':':
605     case ';':
606     case ',':
607     case '+':
608     case '-':
609     case '*':
610     case '/':
611         return true;
612
613     default:
614         return false;
615     }
616 }
617
618 static bool isLiteral(AST::Node *ast)
619 {
620     if (AST::cast<AST::StringLiteral *>(ast))
621         return true;
622     else if (AST::cast<AST::NumericLiteral *>(ast))
623         return true;
624     else
625         return false;
626 }
627
628 bool CodeCompletion::completeUrl(const QString &relativeBasePath, const QString &urlString)
629 {
630     const QUrl url(urlString);
631     QString fileName = url.toLocalFile();
632     if (fileName.isEmpty())
633         return false;
634
635     return completeFileName(relativeBasePath, fileName);
636 }
637
638 bool CodeCompletion::completeFileName(const QString &relativeBasePath, const QString &fileName,
639                                       const QStringList &patterns)
640 {
641     const QFileInfo fileInfo(fileName);
642     QString directoryPrefix;
643     if (fileInfo.isRelative()) {
644         directoryPrefix = relativeBasePath;
645         directoryPrefix += QDir::separator();
646         directoryPrefix += fileInfo.path();
647     } else {
648         directoryPrefix = fileInfo.path();
649     }
650     if (!QFileInfo(directoryPrefix).exists())
651         return false;
652
653     QDirIterator dirIterator(directoryPrefix, patterns, QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
654     while (dirIterator.hasNext()) {
655         dirIterator.next();
656         const QString fileName = dirIterator.fileName();
657
658         TextEditor::CompletionItem item(this);
659         item.text += fileName;
660         // ### Icon for file completions
661         item.icon = iconForColor(Qt::darkBlue);
662         m_completions.append(item);
663     }
664
665     return !m_completions.isEmpty();
666 }
667
668 void CodeCompletion::addCompletions(const QHash<QString, const Interpreter::Value *> &newCompletions,
669                                     const QIcon &icon, int order)
670 {
671     QHashIterator<QString, const Interpreter::Value *> it(newCompletions);
672     while (it.hasNext()) {
673         it.next();
674
675         TextEditor::CompletionItem item(this);
676         item.text = it.key();
677         item.icon = icon;
678         item.order = order;
679         m_completions.append(item);
680     }
681 }
682
683 void CodeCompletion::addCompletions(const QStringList &newCompletions,
684                                     const QIcon &icon, int order)
685 {
686     foreach (const QString &text, newCompletions) {
687         TextEditor::CompletionItem item(this);
688         item.text = text;
689         item.icon = icon;
690         item.order = order;
691         m_completions.append(item);
692     }
693 }
694
695 void CodeCompletion::addCompletionsPropertyLhs(
696         const QHash<QString, const Interpreter::Value *> &newCompletions,
697         const QIcon &icon, int order, bool afterOn)
698 {
699     QHashIterator<QString, const Interpreter::Value *> it(newCompletions);
700     while (it.hasNext()) {
701         it.next();
702
703         TextEditor::CompletionItem item(this);
704         item.text = it.key();
705
706         QLatin1String postfix(": ");
707         if (afterOn)
708             postfix = QLatin1String(" {");
709         if (const Interpreter::QmlObjectValue *qmlValue = dynamic_cast<const Interpreter::QmlObjectValue *>(it.value())) {
710             // to distinguish "anchors." from "gradient:" we check if the right hand side
711             // type is instantiatable or is the prototype of an instantiatable object
712             if (qmlValue->hasChildInPackage())
713                 item.text.append(postfix);
714             else
715                 item.text.append(QLatin1Char('.'));
716         } else {
717             item.text.append(postfix);
718         }
719         item.icon = icon;
720         item.order = order;
721         m_completions.append(item);
722     }
723 }
724
725 static const Interpreter::Value *getPropertyValue(
726     const Interpreter::ObjectValue *object,
727     const QStringList &propertyNames,
728     const Interpreter::Context *context)
729 {
730     if (propertyNames.isEmpty() || !object)
731         return 0;
732
733     const Interpreter::Value *value = object;
734     foreach (const QString &name, propertyNames) {
735         if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) {
736             value = objectValue->property(name, context);
737             if (!value)
738                 return 0;
739         } else {
740             return 0;
741         }
742     }
743     return value;
744 }
745
746 int CodeCompletion::startCompletion(TextEditor::ITextEditor *editor)
747 {
748     m_restartCompletion = false;
749
750     m_editor = editor;
751
752     QmlJSTextEditorWidget *edit = qobject_cast<QmlJSTextEditorWidget *>(m_editor->widget());
753     if (! edit)
754         return -1;
755
756     m_startPosition = editor->position();
757     const QString fileName = editor->file()->fileName();
758
759     while (editor->characterAt(m_startPosition - 1).isLetterOrNumber() ||
760            editor->characterAt(m_startPosition - 1) == QLatin1Char('_'))
761         --m_startPosition;
762
763     m_completions.clear();
764
765     const SemanticInfo semanticInfo = edit->semanticInfo();
766
767     if (! semanticInfo.isValid())
768         return -1;
769
770     const Document::Ptr document = semanticInfo.document;
771     const QFileInfo currentFileInfo(fileName);
772
773     bool isQmlFile = false;
774     if (currentFileInfo.suffix() == QLatin1String("qml"))
775         isQmlFile = true;
776
777     const QIcon symbolIcon = iconForColor(Qt::darkCyan);
778     const QIcon keywordIcon = iconForColor(Qt::darkYellow);
779
780     const QList<AST::Node *> path = semanticInfo.astPath(editor->position());
781     LookupContext::Ptr lookupContext = semanticInfo.lookupContext(path);
782     const Interpreter::Context *context = lookupContext->context();
783
784     // Search for the operator that triggered the completion.
785     QChar completionOperator;
786     if (m_startPosition > 0)
787         completionOperator = editor->characterAt(m_startPosition - 1);
788
789     QTextCursor startPositionCursor(edit->document());
790     startPositionCursor.setPosition(m_startPosition);
791     CompletionContextFinder contextFinder(startPositionCursor);
792
793     const Interpreter::ObjectValue *qmlScopeType = 0;
794     if (contextFinder.isInQmlContext()) {
795         // ### this should use semanticInfo.declaringMember instead, but that may also return functions
796         for (int i = path.size() - 1; i >= 0; --i) {
797             AST::Node *node = path[i];
798             if (AST::cast<AST::UiObjectDefinition *>(node) || AST::cast<AST::UiObjectBinding *>(node)) {
799                 qmlScopeType = document->bind()->findQmlObject(node);
800                 if (qmlScopeType)
801                     break;
802             }
803         }
804         // fallback to getting the base type object
805         if (!qmlScopeType)
806             qmlScopeType = context->lookupType(document.data(), contextFinder.qmlObjectTypeName());
807     }
808
809     if (contextFinder.isInStringLiteral()) {
810         // get the text of the literal up to the cursor position
811         QTextCursor tc = edit->textCursor();
812         QmlExpressionUnderCursor expressionUnderCursor;
813         expressionUnderCursor(tc);
814         QString literalText = expressionUnderCursor.text();
815         QTC_ASSERT(!literalText.isEmpty() && (
816                        literalText.at(0) == QLatin1Char('"')
817                        || literalText.at(0) == QLatin1Char('\'')), return -1);
818         literalText = literalText.mid(1);
819
820         if (contextFinder.isInImport()) {
821             QStringList patterns;
822             patterns << QLatin1String("*.qml") << QLatin1String("*.js");
823             if (completeFileName(document->path(), literalText, patterns))
824                 return m_startPosition;
825             return -1;
826         }
827
828         const Interpreter::Value *value = getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context);
829         if (!value) {
830             // do nothing
831         } else if (value->asUrlValue()) {
832             if (completeUrl(document->path(), literalText))
833                 return m_startPosition;
834         }
835
836         // ### enum completion?
837
838         // completion gets triggered for / in string literals, if we don't
839         // return here, this will mean the snippet completion pops up for
840         // each / in a string literal that is not triggering file completion
841         return -1;
842     } else if (completionOperator.isSpace() || completionOperator.isNull() || isDelimiter(completionOperator) ||
843                (completionOperator == QLatin1Char('(') && m_startPosition != editor->position())) {
844
845         bool doGlobalCompletion = true;
846         bool doQmlKeywordCompletion = true;
847         bool doJsKeywordCompletion = true;
848         bool doQmlTypeCompletion = false;
849
850         if (contextFinder.isInLhsOfBinding() && qmlScopeType) {
851             doGlobalCompletion = false;
852             doJsKeywordCompletion = false;
853             doQmlTypeCompletion = true;
854
855             EnumerateProperties enumerateProperties(context);
856             enumerateProperties.setGlobalCompletion(true);
857             enumerateProperties.setEnumerateGeneratedSlots(true);
858
859             // id: is special
860             TextEditor::CompletionItem idPropertyCompletion(this);
861             idPropertyCompletion.text = QLatin1String("id: ");
862             idPropertyCompletion.icon = symbolIcon;
863             idPropertyCompletion.order = PropertyOrder;
864             m_completions.append(idPropertyCompletion);
865
866             addCompletionsPropertyLhs(enumerateProperties(qmlScopeType), symbolIcon, PropertyOrder, contextFinder.isAfterOnInLhsOfBinding());
867
868             if (ScopeBuilder::isPropertyChangesObject(context, qmlScopeType)
869                     && context->scopeChain().qmlScopeObjects.size() == 2) {
870                 addCompletions(enumerateProperties(context->scopeChain().qmlScopeObjects.first()), symbolIcon, SymbolOrder);
871             }
872         }
873
874         if (contextFinder.isInRhsOfBinding() && qmlScopeType) {
875             doQmlKeywordCompletion = false;
876
877             // complete enum values for enum properties
878             const Interpreter::Value *value = getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context);
879             if (const Interpreter::QmlEnumValue *enumValue = dynamic_cast<const Interpreter::QmlEnumValue *>(value)) {
880                 foreach (const QString &key, enumValue->keys()) {
881                     TextEditor::CompletionItem item(this);
882                     item.text = key;
883                     item.data = QString("\"%1\"").arg(key);
884                     item.icon = symbolIcon;
885                     item.order = EnumValueOrder;
886                     m_completions.append(item);
887                 }
888             }
889         }
890
891         if (!contextFinder.isInImport() && !contextFinder.isInQmlContext())
892             doQmlTypeCompletion = true;
893
894         if (doQmlTypeCompletion) {
895             if (const Interpreter::ObjectValue *qmlTypes = context->scopeChain().qmlTypes) {
896                 EnumerateProperties enumerateProperties(context);
897                 addCompletions(enumerateProperties(qmlTypes), symbolIcon, TypeOrder);
898             }
899         }
900
901         if (doGlobalCompletion) {
902             // It's a global completion.
903             EnumerateProperties enumerateProperties(context);
904             enumerateProperties.setGlobalCompletion(true);
905             addCompletions(enumerateProperties(), symbolIcon, SymbolOrder);
906         }
907
908         if (doJsKeywordCompletion) {
909             // add js keywords
910             addCompletions(Scanner::keywords(), keywordIcon, KeywordOrder);
911         }
912
913         // add qml extra words
914         if (doQmlKeywordCompletion && isQmlFile) {
915             static QStringList qmlWords;
916             static QStringList qmlWordsAlsoInJs;
917
918             if (qmlWords.isEmpty()) {
919                 qmlWords << QLatin1String("property")
920                         //<< QLatin1String("readonly")
921                         << QLatin1String("signal")
922                         << QLatin1String("import");
923             }
924             if (qmlWordsAlsoInJs.isEmpty()) {
925                 qmlWordsAlsoInJs << QLatin1String("default")
926                         << QLatin1String("function");
927             }
928
929             addCompletions(qmlWords, keywordIcon, KeywordOrder);
930             if (!doJsKeywordCompletion)
931                 addCompletions(qmlWordsAlsoInJs, keywordIcon, KeywordOrder);
932         }
933     }
934
935     else if (completionOperator == QLatin1Char('.') || completionOperator == QLatin1Char('(')) {
936         // Look at the expression under cursor.
937         QTextCursor tc = edit->textCursor();
938         tc.setPosition(m_startPosition - 1);
939
940         QmlExpressionUnderCursor expressionUnderCursor;
941         QmlJS::AST::ExpressionNode *expression = expressionUnderCursor(tc);
942
943         if (expression != 0 && ! isLiteral(expression)) {
944             // Evaluate the expression under cursor.
945             Interpreter::Engine *interp = lookupContext->engine();
946             const Interpreter::Value *value = interp->convertToObject(lookupContext->evaluate(expression));
947             //qDebug() << "type:" << interp.typeId(value);
948
949             if (value && completionOperator == QLatin1Char('.')) { // member completion
950                 EnumerateProperties enumerateProperties(context);
951                 if (contextFinder.isInLhsOfBinding() && qmlScopeType) {
952                     enumerateProperties.setEnumerateGeneratedSlots(true);
953                     addCompletionsPropertyLhs(enumerateProperties(value), symbolIcon, PropertyOrder, contextFinder.isAfterOnInLhsOfBinding());
954                 } else
955                     addCompletions(enumerateProperties(value), symbolIcon, SymbolOrder);
956             } else if (value && completionOperator == QLatin1Char('(') && m_startPosition == editor->position()) {
957                 // function completion
958                 if (const Interpreter::FunctionValue *f = value->asFunctionValue()) {
959                     QString functionName = expressionUnderCursor.text();
960                     int indexOfDot = functionName.lastIndexOf(QLatin1Char('.'));
961                     if (indexOfDot != -1)
962                         functionName = functionName.mid(indexOfDot + 1);
963
964                     // Recreate if necessary
965                     if (!m_functionArgumentWidget)
966                         m_functionArgumentWidget = new QmlJSEditor::Internal::FunctionArgumentWidget;
967
968                     QStringList signature;
969                     for (int i = 0; i < f->argumentCount(); ++i)
970                         signature.append(f->argumentName(i));
971
972                     m_functionArgumentWidget->showFunctionHint(functionName.trimmed(),
973                                                                signature,
974                                                                m_startPosition);
975                 }
976
977                 return -1; // We always return -1 when completing function prototypes.
978             }
979         }
980
981         if (! m_completions.isEmpty())
982             return m_startPosition;
983
984         return -1;
985     }
986
987     if (isQmlFile && (completionOperator.isNull() || completionOperator.isSpace() || isDelimiter(completionOperator))) {
988         m_completions.append(m_snippetProvider.getSnippets(this));
989     }
990
991     if (! m_completions.isEmpty())
992         return m_startPosition;
993
994     return -1;
995 }
996
997 void CodeCompletion::completions(QList<TextEditor::CompletionItem> *completions)
998 {
999     const int length = m_editor->position() - m_startPosition;
1000
1001     if (length == 0)
1002         *completions = m_completions;
1003     else if (length > 0) {
1004         const QString key = m_editor->textAt(m_startPosition, length);
1005
1006         filter(m_completions, completions, key);
1007
1008         if (completions->size() == 1) {
1009             if (key == completions->first().text)
1010                 completions->clear();
1011         }
1012     }
1013 }
1014
1015 bool CodeCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar)
1016 {
1017     if (item.data.canConvert<QString>()) // snippet
1018         return false;
1019
1020     return (item.text.endsWith(QLatin1String(": ")) && typedChar == QLatin1Char(':'))
1021             || (item.text.endsWith(QLatin1Char('.')) && typedChar == QLatin1Char('.'));
1022 }
1023
1024 void CodeCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar)
1025 {
1026     Q_UNUSED(typedChar) // Currently always included in the completion item when used
1027
1028     QString toInsert = item.text;
1029
1030     if (QmlJSTextEditorWidget *edit = qobject_cast<QmlJSTextEditorWidget *>(m_editor->widget())) {
1031         if (item.data.isValid()) {
1032             QTextCursor tc = edit->textCursor();
1033             tc.setPosition(m_startPosition, QTextCursor::KeepAnchor);
1034             toInsert = item.data.toString();
1035             edit->insertCodeSnippet(tc, toInsert);
1036             return;
1037         }
1038     }
1039
1040     QString replacableChars;
1041     if (toInsert.endsWith(QLatin1String(": ")))
1042         replacableChars = QLatin1String(": ");
1043     else if (toInsert.endsWith(QLatin1Char('.')))
1044         replacableChars = QLatin1String(".");
1045
1046     int replacedLength = 0;
1047
1048     // Avoid inserting characters that are already there
1049     for (int i = 0; i < replacableChars.length(); ++i) {
1050         const QChar a = replacableChars.at(i);
1051         const QChar b = m_editor->characterAt(m_editor->position() + i);
1052         if (a == b)
1053             ++replacedLength;
1054         else
1055             break;
1056     }
1057
1058     const int length = m_editor->position() - m_startPosition + replacedLength;
1059     m_editor->setCursorPosition(m_startPosition);
1060     m_editor->replace(length, toInsert);
1061
1062     if (toInsert.endsWith(QLatin1Char('.')))
1063         m_restartCompletion = true;
1064 }
1065
1066 bool CodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems)
1067 {
1068     if (completionItems.count() == 1) {
1069         const TextEditor::CompletionItem item = completionItems.first();
1070
1071         if (!item.data.canConvert<QString>()) {
1072             complete(item, QChar());
1073             return true;
1074         }
1075     }
1076
1077     return TextEditor::ICompletionCollector::partiallyComplete(completionItems);
1078 }
1079
1080 void CodeCompletion::cleanup()
1081 {
1082     m_editor = 0;
1083     m_startPosition = 0;
1084     m_completions.clear();
1085 }
1086
1087 static bool qmlCompletionItemLessThan(const TextEditor::CompletionItem &l, const TextEditor::CompletionItem &r)
1088 {
1089     if (l.order != r.order)
1090         return l.order > r.order;
1091     else if (l.text.isEmpty())
1092         return true;
1093     else if (r.text.isEmpty())
1094         return false;
1095     else if (l.data.isValid() != r.data.isValid())
1096         return l.data.isValid();
1097     else if (l.text.at(0).isUpper() && r.text.at(0).isLower())
1098         return false;
1099     else if (l.text.at(0).isLower() && r.text.at(0).isUpper())
1100         return true;
1101
1102     return l.text < r.text;
1103 }
1104
1105 void CodeCompletion::sortCompletion(QList<TextEditor::CompletionItem> &completionItems)
1106 {
1107     qStableSort(completionItems.begin(), completionItems.end(), qmlCompletionItemLessThan);
1108 }
1109
1110 QList<TextEditor::CompletionItem> CodeCompletion::getCompletions()
1111 {
1112     QList<TextEditor::CompletionItem> completionItems;
1113
1114     completions(&completionItems);
1115
1116     sortCompletion(completionItems);
1117
1118     // Remove duplicates
1119     QString lastKey;
1120     QVariant lastData;
1121     QList<TextEditor::CompletionItem> uniquelist;
1122
1123     foreach (const TextEditor::CompletionItem &item, completionItems) {
1124         if (item.text != lastKey || item.data.type() != lastData.type()) {
1125             uniquelist.append(item);
1126             lastKey = item.text;
1127             lastData = item.data;
1128         }
1129     }
1130
1131     return uniquelist;
1132 }