diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp index 513773d34fdfe28c7f7c63222a783a01e4c364f2..58900fec9328156b7b1c0f3c6e4f2de4bed34719 100644 --- a/src/libs/qmljs/qmljsdocument.cpp +++ b/src/libs/qmljs/qmljsdocument.cpp @@ -42,8 +42,7 @@ using namespace QmlJS::AST; Document::Document(const QString &fileName) : _engine(0) , _pool(0) - , _uiProgram(0) - , _jsProgram(0) + , _ast(0) , _fileName(fileName) , _parsedCorrectly(false) { @@ -74,20 +73,17 @@ Document::Ptr Document::create(const QString &fileName) AST::UiProgram *Document::qmlProgram() const { - return _uiProgram; + return cast<UiProgram *>(_ast); } AST::Program *Document::jsProgram() const { - return _jsProgram; + return cast<Program *>(_ast); } AST::Node *Document::ast() const { - Q_ASSERT(!_uiProgram || !_jsProgram); - if (_uiProgram) - return _uiProgram; - return _jsProgram; + return _ast; } QList<DiagnosticMessage> Document::diagnosticMessages() const @@ -109,8 +105,7 @@ bool Document::parseQml() { Q_ASSERT(! _engine); Q_ASSERT(! _pool); - Q_ASSERT(! _uiProgram); - Q_ASSERT(! _jsProgram); + Q_ASSERT(! _ast); _engine = new Engine(); _pool = new NodePool(_fileName, _engine); @@ -122,11 +117,11 @@ bool Document::parseQml() lexer.setCode(_source, /*line = */ 1); _parsedCorrectly = parser.parse(); - _uiProgram = parser.ast(); + _ast = parser.ast(); _diagnosticMessages = parser.diagnosticMessages(); - if (_uiProgram) { - for (QmlJS::AST::UiObjectMemberList *iter = _uiProgram->members; iter; iter = iter->next) + if (qmlProgram()) { + for (QmlJS::AST::UiObjectMemberList *iter = qmlProgram()->members; iter; iter = iter->next) if (iter->member) _symbols.append(new SymbolFromFile(_fileName, iter->member)); @@ -143,8 +138,7 @@ bool Document::parseJavaScript() { Q_ASSERT(! _engine); Q_ASSERT(! _pool); - Q_ASSERT(! _uiProgram); - Q_ASSERT(! _jsProgram); + Q_ASSERT(! _ast); _engine = new Engine(); _pool = new NodePool(_fileName, _engine); @@ -156,7 +150,31 @@ bool Document::parseJavaScript() lexer.setCode(_source, /*line = */ 1); _parsedCorrectly = parser.parseProgram(); - _jsProgram = cast<Program*>(parser.rootNode()); + _ast = cast<Program*>(parser.rootNode()); + _diagnosticMessages = parser.diagnosticMessages(); + + return _parsedCorrectly; +} + +bool Document::parseExpression() +{ + Q_ASSERT(! _engine); + Q_ASSERT(! _pool); + Q_ASSERT(! _ast); + + _engine = new Engine(); + _pool = new NodePool(_fileName, _engine); + _ids.clear(); + + Lexer lexer(_engine); + Parser parser(_engine); + + lexer.setCode(_source, /*line = */ 1); + + _parsedCorrectly = parser.parseExpression(); + _ast = parser.rootNode(); + if (_ast) + _ast = _ast->expressionCast(); _diagnosticMessages = parser.diagnosticMessages(); return _parsedCorrectly; diff --git a/src/libs/qmljs/qmljsdocument.h b/src/libs/qmljs/qmljsdocument.h index 98bedda327d7c7fa52a1ad1692a4e7d850a5831e..3ea931746a3871a6ad63744c0e42c8f314451da8 100644 --- a/src/libs/qmljs/qmljsdocument.h +++ b/src/libs/qmljs/qmljsdocument.h @@ -67,6 +67,7 @@ public: bool parseQml(); bool parseJavaScript(); + bool parseExpression(); bool isParsedCorrectly() const { return _parsedCorrectly; } @@ -84,8 +85,7 @@ public: private: QmlJS::Engine *_engine; QmlJS::NodePool *_pool; - QmlJS::AST::UiProgram *_uiProgram; - QmlJS::AST::Program *_jsProgram; + QmlJS::AST::Node *_ast; QList<QmlJS::DiagnosticMessage> _diagnosticMessages; QString _fileName; QString _path; diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp index 4123ab0f0426526efbe6ea11b81195f2943418a3..21afc6666eeafa7511df70630abd63c320b07a6b 100644 --- a/src/plugins/qmljseditor/qmlcodecompletion.cpp +++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp @@ -50,6 +50,7 @@ using namespace QmlJSEditor; using namespace QmlJSEditor::Internal; +using namespace QmlJS; // Temporary workaround until we have proper icons for QML completion items @@ -83,6 +84,7 @@ static QIcon iconForColor(const QColor &color) return pix; } +namespace { class Evaluate: public QmlJS::AST::Visitor { @@ -181,6 +183,46 @@ protected: }; +class EnumerateProperties +{ + QSet<const Interpreter::ObjectValue *> _processed; + QHash<QString, const Interpreter::Value *> _properties; + +public: + QHash<QString, const Interpreter::Value *> operator()(const Interpreter::Value *value) + { + _processed.clear(); + _properties.clear(); + enumerateProperties(value); + return _properties; + } + +private: + void enumerateProperties(const Interpreter::Value *value) + { + if (! value) + return; + else if (const Interpreter::ObjectValue *object = value->asObjectValue()) { + enumerateProperties(object); + } + } + + void enumerateProperties(const Interpreter::ObjectValue *object) + { + if (! object || _processed.contains(object)) + return; + + _processed.insert(object); + enumerateProperties(object->prototype()); + + for (Interpreter::ObjectValue::MemberIterator it = object->firstMember(); it != object->lastMember(); ++it) { + _properties.insert(it.key(), it.value()); + } + } +}; + +} // end of anonymous namespace + QmlCodeCompletion::QmlCodeCompletion(QmlModelManagerInterface *modelManager, QmlJS::TypeSystem *typeSystem, QObject *parent) : TextEditor::ICompletionCollector(parent), m_modelManager(modelManager), @@ -248,32 +290,33 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) const QIcon typeIcon = iconForColor(Qt::yellow); - foreach (QmlJS::Document::Ptr doc, snapshot) { - const QFileInfo fileInfo(doc->fileName()); - - if (fileInfo.suffix() != QLatin1String("qml")) - continue; - else if (fileInfo.absolutePath() != currentFilePath) // ### FIXME includ `imported' components - continue; - - const QString typeName = fileInfo.baseName(); - if (typeName.isEmpty()) - continue; - - if (typeName.at(0).isUpper()) { - TextEditor::CompletionItem item(this); - item.text = typeName; - item.icon = typeIcon; - m_completions.append(item); - } - } - QChar previousChar; if (m_startPosition > 0) previousChar = editor->characterAt(m_startPosition - 1); if (previousChar.isSpace() || previousChar.isNull()) { - // ### FIXME + // Add the visible components to the completion box. + foreach (QmlJS::Document::Ptr doc, snapshot) { + const QFileInfo fileInfo(doc->fileName()); + + if (fileInfo.suffix() != QLatin1String("qml")) + continue; + else if (fileInfo.absolutePath() != currentFilePath) // ### FIXME includ `imported' components + continue; + + const QString typeName = fileInfo.baseName(); + if (typeName.isEmpty()) + continue; + + if (typeName.at(0).isUpper()) { + TextEditor::CompletionItem item(this); + item.text = typeName; + item.icon = typeIcon; + m_completions.append(item); + } + } + + // Add the visible IDs to the completion box const QIcon idIcon = iconForColor(Qt::darkGray); QStringList ids = qmlDocument->ids().keys(); foreach (const QString &id, ids) { @@ -287,54 +330,53 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) } } -#if 0 - // FIXME: this completion strategy is not going to work when the document was never parsed correctly. - if (qmlDocument->qmlProgram() != 0) { - const QIcon otherIcon = iconForColor(Qt::darkCyan); + if (previousChar == QLatin1Char('.')) { + const int endOfExpression = m_startPosition - 1; + int startOfExpression = endOfExpression - 2; - // qDebug() << "*** program:" << program; - QmlExpressionUnderCursor expressionUnderCursor; - QTextCursor cursor(edit->document()); - cursor.setPosition(pos); - expressionUnderCursor(cursor, qmlDocument); + while (startOfExpression >= 0) { + const QChar ch = editor->characterAt(startOfExpression); - QmlLookupContext context(expressionUnderCursor.expressionScopes(), qmlDocument, m_modelManager->snapshot(), m_typeSystem); - QmlResolveExpression resolver(context); + if (ch.isLetterOrNumber() || ch == QLatin1Char('_') || ch == QLatin1Char('.')) + --startOfExpression; + else + break; + } + ++startOfExpression; - QmlJS::AST::Node *expr = expressionUnderCursor.expressionNode(); + const QString expression = m_editor->textAt(startOfExpression, endOfExpression - startOfExpression); + //qDebug() << "expression:" << expression; - QmlJS::Interpreter::Engine engine; - Evaluate evaluate(&engine); - if (const QmlJS::Interpreter::Value *value = evaluate(expr)) { - if (const QmlJS::Interpreter::ObjectValue *object = value->asObjectValue()) { - for (QmlJS::Interpreter::ObjectValue::MemberIterator it = object->firstMember(); it != object->lastMember(); ++it) { - TextEditor::CompletionItem item(this); - item.text = it.key(); - item.icon = otherIcon; - m_completions.append(item); - } - return pos; - } - } + QmlJS::Document::Ptr exprDoc = QmlJS::Document::create(QLatin1String("<expression>")); + exprDoc->setSource(expression); + exprDoc->parseExpression(); + + if (exprDoc->ast()) { + Interpreter::Engine interp; + Evaluate evaluate(&interp); + + const Interpreter::Value *value = interp.convertToObject(evaluate(exprDoc->ast())); + //qDebug() << "type:" << interp.typeId(value); - // qDebug()<<"*** expression under cursor:"<<expressionUnderCursor.expressionNode(); - const QList<QmlJS::Symbol*> symbols = resolver.visibleSymbols(expressionUnderCursor.expressionNode()); - // qDebug()<<"***"<<symbols.size()<<"visible symbols"; + const QIcon symbolIcon = iconForColor(Qt::darkCyan); - foreach (QmlJS::Symbol *symbol, symbols) { - if (symbol->isIdSymbol()) - continue; // nothing to do here. + EnumerateProperties enumerateProperties; + QHashIterator<QString, const Interpreter::Value *> it(enumerateProperties(value)); + while (it.hasNext()) { + it.next(); - const QString word = symbol->name(); - if (! word.isEmpty()) { TextEditor::CompletionItem item(this); - item.text = word; - item.icon = otherIcon; + item.text = it.key(); + item.icon = symbolIcon; m_completions.append(item); } } + + if (! m_completions.isEmpty()) + return m_startPosition; + + return -1; } -#endif if (previousChar.isNull() || previousChar.isSpace()