From 12dffc659097ee54f831cd742b63f477e4ad6afe Mon Sep 17 00:00:00 2001 From: Nikita Baryshnikov Date: Thu, 8 Jan 2015 22:43:39 +0300 Subject: [PATCH] Qml&Js: properties/methods/enums inspection .. of builtIn qml and cpp code over "Inspect API For Element Under Cursor" action. Change-Id: I70d5bec2933b682295c5242248a2b0f95dba4e76 Reviewed-by: Kai Koehne --- src/libs/qmljs/qmljsinterpreter.cpp | 59 +++---- src/libs/qmljs/qmljsinterpreter.h | 20 ++- src/plugins/qmljseditor/qmljseditor.cpp | 167 +++++++++++++++++- src/plugins/qmljseditor/qmljseditor.h | 3 + .../qmljseditor/qmljseditorconstants.h | 1 + src/plugins/qmljseditor/qmljseditorplugin.cpp | 9 + 6 files changed, 222 insertions(+), 37 deletions(-) diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp index d750af64f16..5b9c3d2fd9e 100644 --- a/src/libs/qmljs/qmljsinterpreter.cpp +++ b/src/libs/qmljs/qmljsinterpreter.cpp @@ -137,40 +137,37 @@ public: } // end of anonymous namespace namespace QmlJS { -namespace Internal { -class MetaFunction: public FunctionValue + +MetaFunction::MetaFunction(const FakeMetaMethod &method, ValueOwner *valueOwner) + : FunctionValue(valueOwner), m_method(method) { - FakeMetaMethod m_method; +} -public: - MetaFunction(const FakeMetaMethod &method, ValueOwner *valueOwner) - : FunctionValue(valueOwner), m_method(method) - { - } +int MetaFunction::namedArgumentCount() const +{ + return m_method.parameterNames().size(); +} - virtual int namedArgumentCount() const - { - return m_method.parameterNames().size(); - } +QString MetaFunction::argumentName(int index) const +{ + if (index < m_method.parameterNames().size()) + return m_method.parameterNames().at(index); - virtual QString argumentName(int index) const - { - if (index < m_method.parameterNames().size()) - return m_method.parameterNames().at(index); + return FunctionValue::argumentName(index); +} - return FunctionValue::argumentName(index); - } - - virtual bool isVariadic() const - { - return false; - } - const MetaFunction *asMetaFunction() const - { - return this; - } -}; -} // namespace Internal +bool MetaFunction::isVariadic() const +{ + return false; +} +const MetaFunction *MetaFunction::asMetaFunction() const +{ + return this; +} +const FakeMetaMethod &MetaFunction::fakeMetaMethod() const +{ + return m_method; +} FakeMetaObjectWithOrigin::FakeMetaObjectWithOrigin(FakeMetaObject::ConstPtr fakeMetaObject, const QString &originId) : fakeMetaObject(fakeMetaObject) @@ -293,7 +290,7 @@ void CppComponentValue::processMembers(MemberProcessor *processor) const signatures = new QList; signatures->reserve(m_metaObject->methodCount()); for (int index = 0; index < m_metaObject->methodCount(); ++index) - signatures->append(new Internal::MetaFunction(m_metaObject->method(index), valueOwner())); + signatures->append(new MetaFunction(m_metaObject->method(index), valueOwner())); if (!m_metaSignatures.testAndSetOrdered(0, signatures)) { delete signatures; signatures = m_metaSignatures.load(); @@ -830,7 +827,7 @@ const Function *Value::asFunction() const return 0; } -const Internal::MetaFunction *Value::asMetaFunction() const +const MetaFunction *Value::asMetaFunction() const { return 0; } diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h index 6b8ffe549d3..ed707cd1635 100644 --- a/src/libs/qmljs/qmljsinterpreter.h +++ b/src/libs/qmljs/qmljsinterpreter.h @@ -83,10 +83,10 @@ class UnknownValue; class UrlValue; class Value; class ValueOwner; +class MetaFunction; typedef QSharedPointer ContextPtr; namespace Internal { -class MetaFunction; class QtObjectPrototypeReference; } // namespace Internal @@ -150,7 +150,7 @@ public: virtual const ASTSignal *asAstSignal() const; virtual const ASTFunctionValue *asAstFunctionValue() const; virtual const Function *asFunction() const; - virtual const Internal::MetaFunction *asMetaFunction() const; + virtual const MetaFunction *asMetaFunction() const; virtual const JSImportScope *asJSImportScope() const; virtual const TypeScope *asTypeScope() const; @@ -304,7 +304,7 @@ template <> Q_INLINE_TEMPLATE const Function *value_cast(const Value *v) else return 0; } -template <> Q_INLINE_TEMPLATE const Internal::MetaFunction*value_cast(const Value *v) +template <> Q_INLINE_TEMPLATE const MetaFunction *value_cast(const Value *v) { if (v) return v->asMetaFunction(); else return 0; @@ -1110,6 +1110,20 @@ private: bool m_importFailed; }; +class QMLJS_EXPORT MetaFunction: public FunctionValue +{ + LanguageUtils::FakeMetaMethod m_method; + +public: + MetaFunction(const LanguageUtils::FakeMetaMethod &method, ValueOwner *valueOwner); + + int namedArgumentCount() const override; + QString argumentName(int index) const override; + bool isVariadic() const override; + const MetaFunction *asMetaFunction() const override; + const LanguageUtils::FakeMetaMethod &fakeMetaMethod() const; +}; + } // namespace QmlJS #endif // QMLJS_INTERPRETER_H diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index c1af7211134..13a85a8c5fc 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -36,6 +36,7 @@ #include "qmljseditordocument.h" #include "qmljseditorplugin.h" #include "qmljsfindreferences.h" +#include "qmljshighlighter.h" #include "qmljshoverhandler.h" #include "qmljsquickfixassist.h" #include "qmloutlinemodel.h" @@ -72,10 +73,10 @@ #include #include -#include -#include -#include #include +#include +#include +#include #include #include @@ -556,6 +557,166 @@ void QmlJSEditorWidget::createToolBar() insertExtraToolBarWidget(TextEditorWidget::Left, m_outlineCombo); } +class CodeModelInspector : public MemberProcessor +{ +public: + explicit CodeModelInspector(const CppComponentValue *processingValue, QTextStream *stream) : + m_processingValue(processingValue), + m_stream(stream), + m_indent(QLatin1String(" ")) + { + } + + bool processProperty(const QString &name, const Value *value, + const PropertyInfo &propertyInfo) override + { + QString type; + if (const CppComponentValue *cpp = value->asCppComponentValue()) + type = cpp->metaObject()->className(); + else + type = m_processingValue->propertyType(name); + + if (propertyInfo.isList()) + type = QStringLiteral("list<%1>").arg(type); + + *m_stream << m_indent; + if (!propertyInfo.isWriteable()) + *m_stream << "readonly "; + *m_stream << "property " << type << " " << name << endl; + + return true; + } + bool processSignal(const QString &name, const Value *value) override + { + *m_stream << m_indent << "signal " << name << stringifyFunctionParameters(value) << endl; + return true; + } + bool processSlot(const QString &name, const Value *value) override + { + *m_stream << m_indent << "function " << name << stringifyFunctionParameters(value) << endl; + return true; + } + bool processGeneratedSlot(const QString &name, const Value *value) override + { + *m_stream << m_indent << "/*generated*/ function " << name + << stringifyFunctionParameters(value) << endl; + return true; + } + +private: + QString stringifyFunctionParameters(const Value *value) const + { + QStringList params; + const QmlJS::MetaFunction *metaFunction = value->asMetaFunction(); + if (metaFunction) { + QStringList paramNames = metaFunction->fakeMetaMethod().parameterNames(); + QStringList paramTypes = metaFunction->fakeMetaMethod().parameterTypes(); + for (int i = 0; i < paramTypes.size(); ++i) { + QString typeAndNamePair = paramTypes.at(i); + if (paramNames.size() > i) { + QString paramName = paramNames.at(i); + if (!paramName.isEmpty()) + typeAndNamePair += QLatin1Char(' ') + paramName; + } + params.append(typeAndNamePair); + } + } + return QLatin1Char('(') + params.join(QLatin1String(", ")) + QLatin1Char(')'); + } + +private: + const CppComponentValue *m_processingValue; + QTextStream *m_stream; + const QString m_indent; +}; + +static const CppComponentValue *findCppComponentToInspect(const SemanticInfo &semanticInfo, + const unsigned cursorPosition) +{ + AST::Node *node = semanticInfo.astNodeAt(cursorPosition); + if (!node) + return 0; + + const ScopeChain scopeChain = semanticInfo.scopeChain(semanticInfo.rangePath(cursorPosition)); + Evaluate evaluator(&scopeChain); + const Value *value = evaluator.reference(node); + if (!value) + return 0; + + return value->asCppComponentValue(); +} + +static QString inspectCppComponent(const CppComponentValue *cppValue) +{ + QString result; + QTextStream bufWriter(&result); + + // for QtObject + QString superClassName = cppValue->metaObject()->superclassName(); + if (superClassName.isEmpty()) + superClassName = cppValue->metaObject()->className(); + + bufWriter << "import QtQuick " << cppValue->importVersion().toString() << endl + << "// " << cppValue->metaObject()->className() + << " imported as " << cppValue->moduleName() << " " + << cppValue->importVersion().toString() << endl + << endl + << superClassName << " {" << endl; + + CodeModelInspector insp(cppValue, &bufWriter); + cppValue->processMembers(&insp); + + bufWriter << endl; + const int enumeratorCount = cppValue->metaObject()->enumeratorCount(); + for (int index = cppValue->metaObject()->enumeratorOffset(); index < enumeratorCount; ++index) { + LanguageUtils::FakeMetaEnum enumerator = cppValue->metaObject()->enumerator(index); + bufWriter << " // Enum " << enumerator.name() << " { " << + enumerator.keys().join(QLatin1Char(',')) << " }" << endl; + } + + bufWriter << "}" << endl; + return result; +} + +void QmlJSEditorWidget::inspectElementUnderCursor() const +{ + const QTextCursor cursor = textCursor(); + + const unsigned cursorPosition = cursor.position(); + const SemanticInfo semanticInfo = m_qmlJsEditorDocument->semanticInfo(); + if (!semanticInfo.isValid()) + return; + + const CppComponentValue *cppValue = findCppComponentToInspect(semanticInfo, cursorPosition); + if (!cppValue) { + QString title = tr("Code Model Not Available"); + const QString nothingToShow = QStringLiteral("nothingToShow"); + EditorManager::openEditorWithContents(Core::Constants::K_DEFAULT_TEXT_EDITOR_ID, &title, + tr("Code model not available.").toUtf8(), nothingToShow, + EditorManager::IgnoreNavigationHistory); + return; + } + + QString title = tr("Code Model of %1").arg(cppValue->metaObject()->className()); + IEditor *outputEditor = EditorManager::openEditorWithContents( + Core::Constants::K_DEFAULT_TEXT_EDITOR_ID, &title, QByteArray(), + cppValue->metaObject()->className(), EditorManager::IgnoreNavigationHistory); + + if (!outputEditor) + return; + + auto widget = qobject_cast(outputEditor->widget()); + if (!widget) + return; + + widget->setReadOnly(true); + widget->textDocument()->setTemporary(true); + widget->textDocument()->setSyntaxHighlighter(new QmlJSHighlighter(widget->document())); + + const QString buf = inspectCppComponent(cppValue); + widget->textDocument()->setPlainText(buf); +} + TextEditorWidget::Link QmlJSEditorWidget::findLinkAt(const QTextCursor &cursor, bool /*resolveTarget*/, bool /*inNextSplit*/) diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h index 0f126c9504f..475aa62ff31 100644 --- a/src/plugins/qmljseditor/qmljseditor.h +++ b/src/plugins/qmljseditor/qmljseditor.h @@ -75,6 +75,9 @@ public: TextEditor::AssistInterface *createAssistInterface(TextEditor::AssistKind assistKind, TextEditor::AssistReason reason) const; + + void inspectElementUnderCursor() const; + public slots: void findUsages(); void renameUsages(); diff --git a/src/plugins/qmljseditor/qmljseditorconstants.h b/src/plugins/qmljseditor/qmljseditorconstants.h index 7dde902b7ff..9ae56afd15b 100644 --- a/src/plugins/qmljseditor/qmljseditorconstants.h +++ b/src/plugins/qmljseditor/qmljseditorconstants.h @@ -51,6 +51,7 @@ const char RENAME_USAGES[] = "QmlJSEditor.RenameUsages"; const char RUN_SEMANTIC_SCAN[] = "QmlJSEditor.RunSemanticScan"; const char REFORMAT_FILE[] = "QmlJSEditor.ReformatFile"; const char SHOW_QT_QUICK_HELPER[] = "QmlJSEditor.ShowQtQuickHelper"; +const char INSPECT_ELEMENT_UNDER_CURSOR[] = "QmlJSEditor.InspectElementUnderCursor"; const char TASK_CATEGORY_QML[] = "Task.Category.Qml"; const char TASK_CATEGORY_QML_ANALYSIS[] = "Task.Category.QmlAnalysis"; diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index 557935896b2..cf6f201e361 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -158,6 +158,15 @@ bool QmlJSEditorPlugin::initialize(const QStringList & /*arguments*/, QString *e connect(m_reformatFileAction, SIGNAL(triggered()), this, SLOT(reformatFile())); qmlToolsMenu->addAction(cmd); + QAction *inspectElementAction = new QAction(tr("Inspect API for Element Under Cursor"), this); + cmd = ActionManager::registerAction(inspectElementAction, + Id(Constants::INSPECT_ELEMENT_UNDER_CURSOR), context); + connect(inspectElementAction, &QAction::triggered, [] { + if (auto widget = qobject_cast(EditorManager::currentEditor()->widget())) + widget->inspectElementUnderCursor(); + }); + qmlToolsMenu->addAction(cmd); + QAction *showQuickToolbar = new QAction(tr("Show Qt Quick Toolbar"), this); cmd = ActionManager::registerAction(showQuickToolbar, Constants::SHOW_QT_QUICK_HELPER, context); cmd->setDefaultKeySequence(UseMacShortcuts ? QKeySequence(Qt::META + Qt::ALT + Qt::Key_Space)