1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
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.
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.
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.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
31 **************************************************************************/
33 #include "qmljscompletionassist.h"
34 #include "qmljseditorconstants.h"
35 #include "qmljsreuse.h"
36 #include "qmlexpressionundercursor.h"
38 #include <coreplugin/ifile.h>
40 #include <texteditor/codeassist/iassistinterface.h>
41 #include <texteditor/codeassist/genericproposal.h>
42 #include <texteditor/codeassist/functionhintproposal.h>
43 #include <texteditor/codeassist/ifunctionhintproposalmodel.h>
45 #include <utils/qtcassert.h>
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>
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>
66 using namespace QmlJS;
67 using namespace QmlJSEditor;
68 using namespace Internal;
69 using namespace TextEditor;
73 enum CompletionOrder {
82 static void addCompletion(QList<TextEditor::BasicProposalItem *> *completions,
86 const QVariant &data = QVariant())
91 BasicProposalItem *item = new QmlJSAssistProposalItem;
94 item->setOrder(order);
96 completions->append(item);
99 static void addCompletions(QList<TextEditor::BasicProposalItem *> *completions,
100 const QStringList &newCompletions,
104 foreach (const QString &text, newCompletions)
105 addCompletion(completions, text, icon, order);
108 class PropertyProcessor
111 virtual void operator()(const Value *base, const QString &name, const Value *value) = 0;
114 class CompletionAdder : public PropertyProcessor
117 QList<TextEditor::BasicProposalItem *> *completions;
120 CompletionAdder(QList<TextEditor::BasicProposalItem *> *completions,
121 const QIcon &icon, int order)
122 : completions(completions)
127 virtual void operator()(const Value *base, const QString &name, const Value *value)
131 addCompletion(completions, name, icon, order);
138 class LhsCompletionAdder : public CompletionAdder
141 LhsCompletionAdder(QList<TextEditor::BasicProposalItem *> *completions,
145 : CompletionAdder(completions, icon, order)
149 virtual void operator ()(const Value *base, const QString &name, const Value *)
151 const QmlObjectValue *qmlBase = dynamic_cast<const QmlObjectValue *>(base);
153 QString itemText = name;
155 if (!itemText.isEmpty() && itemText.at(0).isLower())
156 postfix = QLatin1String(": ");
158 postfix = QLatin1String(" {");
160 // readonly pointer properties (anchors, ...) always get a .
161 if (qmlBase && !qmlBase->isWritable(name) && qmlBase->isPointer(name))
162 postfix = QLatin1Char('.');
164 itemText.append(postfix);
166 addCompletion(completions, itemText, icon, order);
172 class ProcessProperties: private MemberProcessor
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;
183 ProcessProperties(const ScopeChain *scopeChain)
184 : _globalCompletion(false),
185 _enumerateGeneratedSlots(false),
186 _enumerateSlots(true),
187 _scopeChain(scopeChain),
189 _propertyProcessor(0)
193 void setGlobalCompletion(bool globalCompletion)
195 _globalCompletion = globalCompletion;
198 void setEnumerateGeneratedSlots(bool enumerate)
200 _enumerateGeneratedSlots = enumerate;
203 void setEnumerateSlots(bool enumerate)
205 _enumerateSlots = enumerate;
208 void operator ()(const Value *value, PropertyProcessor *processor)
211 _propertyProcessor = processor;
213 processProperties(value);
216 void operator ()(PropertyProcessor *processor)
219 _propertyProcessor = processor;
221 foreach (const ObjectValue *scope, _scopeChain->all())
222 processProperties(scope);
226 void process(const QString &name, const Value *value)
228 (*_propertyProcessor)(_currentObject, name, value);
231 virtual bool processProperty(const QString &name, const Value *value)
233 process(name, value);
237 virtual bool processEnumerator(const QString &name, const Value *value)
239 if (! _globalCompletion)
240 process(name, value);
244 virtual bool processSignal(const QString &name, const Value *value)
246 if (_globalCompletion)
247 process(name, value);
251 virtual bool processSlot(const QString &name, const Value *value)
254 process(name, value);
258 virtual bool processGeneratedSlot(const QString &name, const Value *value)
260 if (_enumerateGeneratedSlots || (_currentObject && _currentObject->className().endsWith(QLatin1String("Keys")))) {
261 // ### FIXME: add support for attached properties.
262 process(name, value);
267 void processProperties(const Value *value)
271 else if (const ObjectValue *object = value->asObjectValue()) {
272 processProperties(object);
276 void processProperties(const ObjectValue *object)
278 if (! object || _processed.contains(object))
281 _processed.insert(object);
282 processProperties(object->prototype(_scopeChain->context()));
284 _currentObject = object;
285 object->processMembers(this);
290 const Value *getPropertyValue(const ObjectValue *object,
291 const QStringList &propertyNames,
292 const ContextPtr &context)
294 if (propertyNames.isEmpty() || !object)
297 const Value *value = object;
298 foreach (const QString &name, propertyNames) {
299 if (const ObjectValue *objectValue = value->asObjectValue()) {
300 value = objectValue->lookupMember(name, context);
310 bool isLiteral(AST::Node *ast)
312 if (AST::cast<AST::StringLiteral *>(ast))
314 else if (AST::cast<AST::NumericLiteral *>(ast))
322 // -----------------------
323 // QmlJSAssistProposalItem
324 // -----------------------
325 bool QmlJSAssistProposalItem::prematurelyApplies(const QChar &c) const
327 if (data().canConvert<QString>()) // snippet
330 return (text().endsWith(QLatin1String(": ")) && c == QLatin1Char(':'))
331 || (text().endsWith(QLatin1Char('.')) && c == QLatin1Char('.'));
334 void QmlJSAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor *editor,
335 int basePosition) const
337 const int currentPosition = editor->position();
338 editor->setCursorPosition(basePosition);
339 editor->remove(currentPosition - basePosition);
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);
356 const int length = editor->position() - basePosition + replacedLength;
357 editor->replace(length, content);
360 // -------------------------
361 // FunctionHintProposalModel
362 // -------------------------
363 class FunctionHintProposalModel : public TextEditor::IFunctionHintProposalModel
366 FunctionHintProposalModel(const QString &functionName, const QStringList &signature)
367 : m_functionName(functionName)
368 , m_signature(signature)
369 , m_minimumArgumentCount(signature.size())
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;
378 QString m_functionName;
379 QStringList m_signature;
380 int m_minimumArgumentCount;
383 QString FunctionHintProposalModel::text(int index) const
387 QString prettyMethod;
388 prettyMethod += QString::fromLatin1("function ");
389 prettyMethod += m_functionName;
390 prettyMethod += QLatin1Char('(');
391 for (int i = 0; i < m_minimumArgumentCount; ++i) {
393 prettyMethod += QLatin1String(", ");
395 QString arg = m_signature.at(i);
397 arg = QLatin1String("arg");
398 arg += QString::number(i + 1);
403 prettyMethod += QLatin1Char(')');
407 int FunctionHintProposalModel::activeArgument(const QString &prefix) const
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))
417 else if (tk.is(Token::RightParenthesis))
419 else if (! parcount && tk.is(Token::Colon))
429 // -----------------------------
430 // QmlJSCompletionAssistProvider
431 // -----------------------------
432 bool QmlJSCompletionAssistProvider::supportsEditor(const QString &editorId) const
434 return editorId == QLatin1String(Constants::C_QMLJSEDITOR_ID);
437 int QmlJSCompletionAssistProvider::activationCharSequenceLength() const
442 bool QmlJSCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
444 return isActivationChar(sequence.at(0));
447 bool QmlJSCompletionAssistProvider::isContinuationChar(const QChar &c) const
449 return isIdentifierChar(c, false);
452 IAssistProcessor *QmlJSCompletionAssistProvider::createProcessor() const
454 return new QmlJSCompletionAssistProcessor;
457 // ------------------------------
458 // QmlJSCompletionAssistProcessor
459 // ------------------------------
460 QmlJSCompletionAssistProcessor::QmlJSCompletionAssistProcessor()
462 , m_snippetCollector(Constants::QML_SNIPPETS_GROUP_ID, iconForColor(Qt::red), SnippetOrder)
465 QmlJSCompletionAssistProcessor::~QmlJSCompletionAssistProcessor()
468 IAssistProposal *QmlJSCompletionAssistProcessor::createContentProposal() const
470 IGenericProposalModel *model = new QmlJSAssistProposalModel(m_completions);
471 IAssistProposal *proposal = new GenericProposal(m_startPosition, model);
475 IAssistProposal *QmlJSCompletionAssistProcessor::createHintProposal(const QString &functionName,
476 const QStringList &signature) const
478 IFunctionHintProposalModel *model = new FunctionHintProposalModel(functionName, signature);
479 IAssistProposal *proposal = new FunctionHintProposal(m_startPosition, model);
483 IAssistProposal *QmlJSCompletionAssistProcessor::perform(const IAssistInterface *assistInterface)
485 m_interface.reset(static_cast<const QmlJSCompletionAssistInterface *>(assistInterface));
487 if (assistInterface->reason() == IdleEditor && !acceptsIdleEditor())
490 const QString &fileName = m_interface->file()->fileName();
492 m_startPosition = assistInterface->position();
493 while (isIdentifierChar(m_interface->document()->characterAt(m_startPosition - 1), false, false))
495 const bool onIdentifier = m_startPosition != assistInterface->position();
497 m_completions.clear();
499 const QmlJSCompletionAssistInterface *qmlInterface =
500 static_cast<const QmlJSCompletionAssistInterface *>(assistInterface);
501 const SemanticInfo &semanticInfo = qmlInterface->semanticInfo();
502 if (!semanticInfo.isValid())
505 const Document::Ptr document = semanticInfo.document;
506 const QFileInfo currentFileInfo(fileName);
508 bool isQmlFile = false;
509 if (currentFileInfo.suffix() == QLatin1String("qml"))
512 const QList<AST::Node *> path = semanticInfo.rangePath(m_interface->position());
513 const ContextPtr &context = semanticInfo.context;
514 const ScopeChain &scopeChain = semanticInfo.scopeChain(path);
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);
525 QTextCursor startPositionCursor(qmlInterface->document());
526 startPositionCursor.setPosition(m_startPosition);
527 CompletionContextFinder contextFinder(startPositionCursor);
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
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);
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))
547 const ObjectValue *newScopeType = qmlScopeType;
548 for (AST::UiQualifiedId *it = objDef->qualifiedTypeNameId; it; it = it->next) {
549 if (!newScopeType || it->name.isEmpty()) {
553 const Value *v = newScopeType->lookupMember(it->name.toString(), context);
554 v = context->lookupReference(v);
555 newScopeType = value_cast<const ObjectValue *>(v);
559 qmlScopeType = newScopeType;
561 // fallback to getting the base type object
563 qmlScopeType = context->lookupType(document.data(), contextFinder.qmlObjectTypeName());
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);
579 if (contextFinder.isInImport()) {
580 QStringList patterns;
581 patterns << QLatin1String("*.qml") << QLatin1String("*.js");
582 if (completeFileName(document->path(), literalText, patterns))
583 return createContentProposal();
588 getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context);
591 } else if (value->asUrlValue()) {
592 if (completeUrl(document->path(), literalText))
593 return createContentProposal();
596 // ### enum completion?
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);
608 QmlExpressionUnderCursor expressionUnderCursor;
609 QmlJS::AST::ExpressionNode *expression = expressionUnderCursor(tc);
611 if (expression != 0 && ! isLiteral(expression)) {
612 // Evaluate the expression under cursor.
613 ValueOwner *interp = context->valueOwner();
615 interp->convertToObject(scopeChain.evaluate(expression));
616 //qDebug() << "type:" << interp->typeId(value);
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);
626 CompletionAdder completionAdder(&m_completions, m_interface->symbolIcon(), SymbolOrder);
627 processProperties(value, &completionAdder);
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);
639 QStringList signature;
640 for (int i = 0; i < f->argumentCount(); ++i)
641 signature.append(f->argumentName(i));
643 return createHintProposal(functionName.trimmed(), signature);
648 if (! m_completions.isEmpty())
649 return createContentProposal();
653 else if (onIdentifier || assistInterface->reason() == ExplicitlyInvoked) {
655 bool doGlobalCompletion = true;
656 bool doQmlKeywordCompletion = true;
657 bool doJsKeywordCompletion = true;
658 bool doQmlTypeCompletion = false;
660 if (contextFinder.isInLhsOfBinding() && qmlScopeType) {
661 doGlobalCompletion = false;
662 doJsKeywordCompletion = false;
663 doQmlTypeCompletion = true;
665 ProcessProperties processProperties(&scopeChain);
666 processProperties.setGlobalCompletion(true);
667 processProperties.setEnumerateGeneratedSlots(true);
668 processProperties.setEnumerateSlots(false);
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);
678 LhsCompletionAdder completionAdder(&m_completions, m_interface->symbolIcon(),
679 PropertyOrder, contextFinder.isAfterOnInLhsOfBinding());
680 processProperties(qmlScopeType, &completionAdder);
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);
690 if (contextFinder.isInRhsOfBinding() && qmlScopeType) {
691 doQmlKeywordCompletion = false;
693 // complete enum values for enum properties
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()) {
702 completion = QString("\"%1\"").arg(key);
704 completion = QString("%1.%2").arg(name, key);
705 addCompletion(&m_completions, key, m_interface->symbolIcon(),
706 EnumValueOrder, completion);
711 if (!contextFinder.isInImport() && !contextFinder.isInQmlContext())
712 doQmlTypeCompletion = true;
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);
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);
730 if (doJsKeywordCompletion) {
732 addCompletions(&m_completions, Scanner::keywords(), m_interface->keywordIcon(), KeywordOrder);
735 // add qml extra words
736 if (doQmlKeywordCompletion && isQmlFile) {
737 static QStringList qmlWords;
738 static QStringList qmlWordsAlsoInJs;
740 if (qmlWords.isEmpty()) {
741 qmlWords << QLatin1String("property")
742 //<< QLatin1String("readonly")
743 << QLatin1String("signal")
744 << QLatin1String("import");
746 if (qmlWordsAlsoInJs.isEmpty())
747 qmlWordsAlsoInJs << QLatin1String("default") << QLatin1String("function");
749 addCompletions(&m_completions, qmlWords, m_interface->keywordIcon(), KeywordOrder);
750 if (!doJsKeywordCompletion)
751 addCompletions(&m_completions, qmlWordsAlsoInJs, m_interface->keywordIcon(), KeywordOrder);
754 m_completions.append(m_snippetCollector.collect());
756 if (! m_completions.isEmpty())
757 return createContentProposal();
764 bool QmlJSCompletionAssistProcessor::acceptsIdleEditor() const
766 const int cursorPos = m_interface->position();
768 bool maybeAccept = false;
769 const QChar &charBeforeCursor = m_interface->document()->characterAt(cursorPos - 1);
770 if (isActivationChar(charBeforeCursor)) {
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))) {
780 int startPos = cursorPos - 1;
781 for (; startPos != -1; --startPos) {
782 if (!isIdentifierChar(m_interface->document()->characterAt(startPos)))
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)))
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;
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))
817 if (charBeforeCursor != QLatin1Char('/'))
824 bool QmlJSCompletionAssistProcessor::completeFileName(const QString &relativeBasePath,
825 const QString &fileName,
826 const QStringList &patterns)
828 const QFileInfo fileInfo(fileName);
829 QString directoryPrefix;
830 if (fileInfo.isRelative()) {
831 directoryPrefix = relativeBasePath;
832 directoryPrefix += QDir::separator();
833 directoryPrefix += fileInfo.path();
835 directoryPrefix = fileInfo.path();
837 if (!QFileInfo(directoryPrefix).exists())
840 QDirIterator dirIterator(directoryPrefix,
842 QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
843 while (dirIterator.hasNext()) {
845 const QString fileName = dirIterator.fileName();
847 BasicProposalItem *item = new QmlJSAssistProposalItem;
848 item->setText(fileName);
849 item->setIcon(m_interface->fileNameIcon());
850 m_completions.append(item);
853 return !m_completions.isEmpty();
856 bool QmlJSCompletionAssistProcessor::completeUrl(const QString &relativeBasePath, const QString &urlString)
858 const QUrl url(urlString);
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())
865 } else if (url.scheme().isEmpty()) {
866 // don't trigger completion while typing a scheme
867 if (urlString.endsWith(QLatin1String(":/")))
869 fileName = urlString;
874 return completeFileName(relativeBasePath, fileName);
877 // ------------------------------
878 // QmlJSCompletionAssistInterface
879 // ------------------------------
880 QmlJSCompletionAssistInterface::QmlJSCompletionAssistInterface(QTextDocument *document,
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))
892 const SemanticInfo &QmlJSCompletionAssistInterface::semanticInfo() const
894 return m_semanticInfo;
901 bool operator() (const BasicProposalItem *a, const BasicProposalItem *b)
903 if (a->order() != b->order())
904 return a->order() > b->order();
905 else if (a->text().isEmpty())
907 else if (b->text().isEmpty())
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())
913 else if (a->text().at(0).isLower() && b->text().at(0).isUpper())
915 return a->text() < b->text();
921 // -------------------------
922 // QmlJSAssistProposalModel
923 // -------------------------
924 void QmlJSAssistProposalModel::sort()
926 qSort(currentItems().first, currentItems().second, QmlJSLessThan());
929 bool QmlJSAssistProposalModel::keepPerfectMatch(TextEditor::AssistReason reason) const
931 return reason == ExplicitlyInvoked;