diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp index 349ec8efa609fbb75e29910047bb1db6f38a3034..b1893ce732d449d72fc645aedcfc6229d64d1120 100644 --- a/src/libs/qmljs/qmljsinterpreter.cpp +++ b/src/libs/qmljs/qmljsinterpreter.cpp @@ -243,7 +243,10 @@ const Value *ConvertToString::switchResult(const Value *value) { return previousResult; } -ConvertToObject::ConvertToObject(Engine *engine): _engine(engine), _result(0) {} +ConvertToObject::ConvertToObject(Engine *engine) + : _engine(engine), _result(0) +{ +} const Value *ConvertToObject::operator()(const Value *value) { const Value *previousValue = switchResult(0); @@ -260,6 +263,40 @@ const Value *ConvertToObject::switchResult(const Value *value) { return previousResult; } +void ConvertToObject::visit(const NullValue *value) +{ + _result = value; +} + +void ConvertToObject::visit(const UndefinedValue *) +{ + _result = _engine->nullValue(); +} + +void ConvertToObject::visit(const NumberValue *) +{ + _result = _engine->call(_engine->numberCtor()); +} + +void ConvertToObject::visit(const BooleanValue *) +{ + _result = _engine->call(_engine->booleanCtor()); +} + +void ConvertToObject::visit(const StringValue *) +{ + _result = _engine->call(_engine->stringCtor()); +} + +void ConvertToObject::visit(const ObjectValue *object) +{ + _result = object; +} + +void ConvertToObject::visit(const FunctionValue *object) +{ + _result = object; +} QString TypeId::operator()(const Value *value) { @@ -913,42 +950,3 @@ void ConvertToString::visit(const FunctionValue *object) _result = value_cast<const StringValue *>(toStringMember->call(object)); // ### invoke convert-to-string? } } - -//////////////////////////////////////////////////////////////////////////////// -// convert to object -//////////////////////////////////////////////////////////////////////////////// -void ConvertToObject::visit(const NullValue *value) -{ - _result = value; -} - -void ConvertToObject::visit(const UndefinedValue *) -{ - _result = _engine->nullValue(); -} - -void ConvertToObject::visit(const NumberValue *) -{ - _result = _engine->call(_engine->numberCtor()); -} - -void ConvertToObject::visit(const BooleanValue *) -{ - _result = _engine->call(_engine->booleanCtor()); -} - -void ConvertToObject::visit(const StringValue *) -{ - _result = _engine->call(_engine->stringCtor()); -} - -void ConvertToObject::visit(const ObjectValue *object) -{ - _result = object; -} - -void ConvertToObject::visit(const FunctionValue *object) -{ - _result = object; -} - diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp index dfc3d11bf82750c0310441ddd06a592a05792562..005ea17cc36af45e78c0419f9b90f6fffa4c8173 100644 --- a/src/plugins/qmljseditor/qmlcodecompletion.cpp +++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp @@ -30,13 +30,13 @@ #include "qmlcodecompletion.h" #include "qmljseditor.h" #include "qmlmodelmanagerinterface.h" -#include "qmlexpressionundercursor.h" #include "qmllookupcontext.h" -#include "qmlresolveexpression.h" #include <qmljs/parser/qmljsast_p.h> #include <qmljs/qmljsinterpreter.h> #include <qmljs/qmljssymbol.h> +#include <qmljs/qmljsscanner.h> + #include <texteditor/basetexteditor.h> #include <coreplugin/icore.h> @@ -94,7 +94,102 @@ static QIcon iconForColor(const QColor &color) namespace { -class Evaluate: public QmlJS::AST::Visitor +class ExpressionUnderCursor +{ + QTextCursor _cursor; + QmlJSScanner scanner; + +public: + QString operator()(const QTextCursor &cursor) + { + _cursor = cursor; + + QTextBlock block = _cursor.block(); + const QString blockText = block.text().left(cursor.columnNumber()); + //qDebug() << "block text:" << blockText; + + int startState = block.previous().userState(); + if (startState == -1) + startState = 0; + else + startState = startState & 0xff; + + const QList<Token> originalTokens = scanner(blockText, startState); + QList<Token> tokens; + int skipping = 0; + for (int index = originalTokens.size() - 1; index != -1; --index) { + const Token &tk = originalTokens.at(index); + + if (tk.is(Token::Comment) || tk.is(Token::String) || tk.is(Token::Number)) + continue; + + if (! skipping) { + tokens.append(tk); + + if (tk.is(Token::Identifier)) { + if (index > 0 && originalTokens.at(index - 1).isNot(Token::Dot)) + break; + } + } else { + //qDebug() << "skip:" << blockText.mid(tk.offset, tk.length); + } + + if (tk.is(Token::RightParenthesis)) + ++skipping; + + else if (tk.is(Token::LeftParenthesis)) { + --skipping; + + if (! skipping) + tokens.append(tk); + } + } + + if (! tokens.isEmpty()) { + QString expr; + for (int index = tokens.size() - 1; index >= 0; --index) { + Token tk = tokens.at(index); + expr.append(QLatin1Char(' ')); + expr.append(blockText.midRef(tk.offset, tk.length)); + } + + //qDebug() << "expression under cursor:" << expr; + return expr; + } + + //qDebug() << "no expression"; + return QString(); + } +}; + +class SearchPropertyDefinitions: protected AST::Visitor +{ + QList<AST::UiPublicMember *> _properties; + +public: + QList<AST::UiPublicMember *> operator()(AST::Node *node) + { + _properties.clear(); + if (node) + node->accept(this); + return _properties; + } + + +protected: + using AST::Visitor::visit; + + virtual bool visit(AST::UiPublicMember *member) + { + if (member->propertyToken.isValid()) { + _properties.append(member); + } + + return true; + } +}; + +class Evaluate: protected QmlJS::AST::Visitor { QmlJS::Interpreter::Engine *_interp; const QmlJS::Interpreter::Value *_value; @@ -127,17 +222,20 @@ protected: return previousValue; } - virtual bool preVisit(QmlJS::AST::Node *ast) + virtual bool preVisit(QmlJS::AST::Node *ast) // ### remove me { using namespace QmlJS::AST; if (cast<NumericLiteral *>(ast)) return true; - if (cast<StringLiteral *>(ast)) + else if (cast<StringLiteral *>(ast)) return true; - if (cast<IdentifierExpression *>(ast)) + else if (cast<IdentifierExpression *>(ast)) + return true; + + else if (cast<NestedExpression *>(ast)) return true; else if (cast<FieldMemberExpression *>(ast)) @@ -149,6 +247,11 @@ protected: return false; } + virtual bool visit(QmlJS::AST::NestedExpression *) + { + return true; + } + virtual bool visit(QmlJS::AST::StringLiteral *) { _value = _interp->convertToObject(_interp->stringValue()); @@ -163,13 +266,19 @@ protected: virtual bool visit(QmlJS::AST::IdentifierExpression *ast) { + if (! ast->name) + return false; + _value = _interp->globalObject()->property(ast->name->asString()); return false; } virtual bool visit(QmlJS::AST::FieldMemberExpression *ast) { - if (const QmlJS::Interpreter::Value *base = evaluate(ast->base)) { + if (! ast->name) + return false; + + if (const QmlJS::Interpreter::Value *base = _interp->convertToObject(evaluate(ast->base))) { if (const QmlJS::Interpreter::ObjectValue *obj = base->asObjectValue()) { _value = obj->property(ast->name->asString()); } @@ -366,30 +475,28 @@ void FunctionArgumentWidget::updateArgumentHighlight() updateHintText(); -#if 0 QString str = m_editor->textAt(m_startpos, curpos - m_startpos); int argnr = 0; int parcount = 0; - SimpleLexer tokenize; - QList<SimpleToken> tokens = tokenize(str); + QmlJSScanner tokenize; + const QList<Token> tokens = tokenize(str); for (int i = 0; i < tokens.count(); ++i) { - const SimpleToken &tk = tokens.at(i); - if (tk.is(T_LPAREN)) + const Token &tk = tokens.at(i); + if (tk.is(Token::LeftParenthesis)) ++parcount; - else if (tk.is(T_RPAREN)) + else if (tk.is(Token::RightParenthesis)) --parcount; - else if (! parcount && tk.is(T_COMMA)) + else if (! parcount && tk.is(Token::Colon)) ++argnr; } if (m_currentarg != argnr) { - m_currentarg = argnr; + // m_currentarg = argnr; updateHintText(); } if (parcount < 0) m_popupFrame->close(); -#endif } bool FunctionArgumentWidget::eventFilter(QObject *obj, QEvent *e) @@ -511,12 +618,11 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) if (! edit) return -1; - int pos = editor->position(); + m_startPosition = editor->position(); - while (editor->characterAt(pos - 1).isLetterOrNumber() || editor->characterAt(pos - 1) == QLatin1Char('_')) - --pos; + while (editor->characterAt(m_startPosition - 1).isLetterOrNumber() || editor->characterAt(m_startPosition - 1) == QLatin1Char('_')) + --m_startPosition; - m_startPosition = pos; m_completions.clear(); QmlJS::Document::Ptr qmlDocument = edit->qmlDocument(); @@ -579,20 +685,12 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) } if (completionOperator == QLatin1Char('.') || completionOperator == QLatin1Char('(')) { - const int endOfExpression = m_startPosition - 1; - int startOfExpression = endOfExpression - 2; + ExpressionUnderCursor expressionUnderCursor; - while (startOfExpression >= 0) { - const QChar ch = editor->characterAt(startOfExpression); + QTextCursor tc = edit->textCursor(); + tc.setPosition(m_startPosition - 1); - if (ch.isLetterOrNumber() || ch == QLatin1Char('_') || ch == QLatin1Char('.')) - --startOfExpression; - else - break; - } - ++startOfExpression; - - const QString expression = m_editor->textAt(startOfExpression, endOfExpression - startOfExpression); + const QString expression = expressionUnderCursor(tc); //qDebug() << "expression:" << expression; QmlJS::Document::Ptr exprDoc = QmlJS::Document::create(QLatin1String("<expression>")); @@ -603,6 +701,15 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) Interpreter::Engine interp; Evaluate evaluate(&interp); + SearchPropertyDefinitions searchPropertyDefinitions; + + const QList<AST::UiPublicMember *> properties = searchPropertyDefinitions(qmlDocument->ast()); + foreach (AST::UiPublicMember *prop, properties) { + if (prop->name && prop->memberType && prop->memberType->asString() == QLatin1String("string")) { + interp.globalObject()->setProperty(prop->name->asString(), interp.stringValue()); + } + } + const Interpreter::Value *value = interp.convertToObject(evaluate(exprDoc->ast())); //qDebug() << "type:" << interp.typeId(value); @@ -619,20 +726,18 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) item.icon = symbolIcon; m_completions.append(item); } - } else if (value && completionOperator == QLatin1Char('(')) { if (const Interpreter::FunctionValue *f = value->asFunctionValue()) { QString functionName = expression; int indexOfDot = expression.lastIndexOf(QLatin1Char('.')); if (indexOfDot != -1) functionName = expression.mid(indexOfDot + 1); + // Recreate if necessary if (!m_functionArgumentWidget) m_functionArgumentWidget = new QmlJSEditor::Internal::FunctionArgumentWidget; - m_functionArgumentWidget->showFunctionHint(functionName, - f->argumentCount(), - m_startPosition); + m_functionArgumentWidget->showFunctionHint(functionName.trimmed(), f->argumentCount(), m_startPosition); } return -1; @@ -655,7 +760,10 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor) m_completions.append(m_snippets); } - return pos; + if (! m_completions.isEmpty()) + return m_startPosition; + + return -1; } void QmlCodeCompletion::completions(QList<TextEditor::CompletionItem> *completions)