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 qt-info@nokia.com.
31 **************************************************************************/
33 #include "qmljseditor.h"
34 #include "qmljseditoreditable.h"
35 #include "qmlexpressionundercursor.h"
36 #include "qmljshoverhandler.h"
38 #include <coreplugin/editormanager/ieditor.h>
39 #include <coreplugin/editormanager/editormanager.h>
40 #include <coreplugin/helpmanager.h>
41 #include <extensionsystem/pluginmanager.h>
42 #include <qmljs/qmljsinterpreter.h>
43 #include <qmljs/parser/qmljsast_p.h>
44 #include <qmljs/parser/qmljsastfwd_p.h>
45 #include <qmljs/qmljscheck.h>
46 #include <texteditor/itexteditor.h>
47 #include <texteditor/basetexteditor.h>
48 #include <texteditor/helpitem.h>
49 #include <texteditor/tooltip/tooltip.h>
50 #include <texteditor/tooltip/tipcontents.h>
52 #include <QtCore/QList>
55 using namespace QmlJS;
56 using namespace QmlJSEditor;
57 using namespace QmlJSEditor::Internal;
61 QString textAt(const Document::Ptr doc,
62 const AST::SourceLocation &from,
63 const AST::SourceLocation &to)
65 return doc->source().mid(from.offset, to.end() - from.begin());
68 AST::UiObjectInitializer *nodeInitializer(AST::Node *node)
70 AST::UiObjectInitializer *initializer = 0;
71 if (const AST::UiObjectBinding *binding = AST::cast<const AST::UiObjectBinding *>(node))
72 initializer = binding->initializer;
73 else if (const AST::UiObjectDefinition *definition =
74 AST::cast<const AST::UiObjectDefinition *>(node))
75 initializer = definition->initializer;
80 bool posIsInSource(const unsigned pos, T *node)
83 pos >= node->firstSourceLocation().begin() && pos < node->lastSourceLocation().end()) {
90 HoverHandler::HoverHandler(QObject *parent) : BaseHoverHandler(parent), m_modelManager(0)
93 ExtensionSystem::PluginManager::instance()->getObject<QmlJS::ModelManagerInterface>();
96 bool HoverHandler::acceptEditor(IEditor *editor)
98 QmlJSEditorEditable *qmlEditor = qobject_cast<QmlJSEditorEditable *>(editor);
104 void HoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos)
111 QmlJSEditor::QmlJSTextEditorWidget *qmlEditor = qobject_cast<QmlJSEditor::QmlJSTextEditorWidget *>(editor->widget());
115 if (matchDiagnosticMessage(qmlEditor, pos))
118 const QmlJSEditor::SemanticInfo &semanticInfo = qmlEditor->semanticInfo();
119 if (! semanticInfo.isValid() || semanticInfo.revision() != qmlEditor->editorRevision())
122 QList<AST::Node *> astPath = semanticInfo.astPath(pos);
123 if (astPath.isEmpty())
126 const Document::Ptr qmlDocument = semanticInfo.document;
127 LookupContext::Ptr lookupContext = semanticInfo.lookupContext(astPath);
129 if (matchColorItem(lookupContext, qmlDocument, astPath, pos))
132 AST::Node *node = semanticInfo.nodeUnderCursor(pos);
133 handleOrdinaryMatch(lookupContext, node);
135 TextEditor::HelpItem helpItem = qmlHelpItem(lookupContext, node);
136 if (!helpItem.helpId().isEmpty())
137 setLastHelpItemIdentified(helpItem);
140 bool HoverHandler::matchDiagnosticMessage(QmlJSEditor::QmlJSTextEditorWidget *qmlEditor, int pos)
142 foreach (const QTextEdit::ExtraSelection &sel,
143 qmlEditor->extraSelections(TextEditor::BaseTextEditorWidget::CodeWarningsSelection)) {
144 if (pos >= sel.cursor.selectionStart() && pos <= sel.cursor.selectionEnd()) {
145 setToolTip(sel.format.toolTip());
152 bool HoverHandler::matchColorItem(const LookupContext::Ptr &lookupContext,
153 const Document::Ptr &qmlDocument,
154 const QList<AST::Node *> &astPath,
157 AST::UiObjectInitializer *initializer = nodeInitializer(astPath.last());
161 AST::UiObjectMember *member = 0;
162 for (AST::UiObjectMemberList *list = initializer->members; list; list = list->next) {
163 if (posIsInSource(pos, list->member)) {
164 member = list->member;
172 const Interpreter::Value *value = 0;
173 if (const AST::UiScriptBinding *binding = AST::cast<const AST::UiScriptBinding *>(member)) {
174 if (binding->qualifiedId && posIsInSource(pos, binding->statement)) {
175 value = lookupContext->evaluate(binding->qualifiedId);
176 if (value && value->asColorValue()) {
177 color = textAt(qmlDocument,
178 binding->statement->firstSourceLocation(),
179 binding->statement->lastSourceLocation());
182 } else if (const AST::UiPublicMember *publicMember =
183 AST::cast<const AST::UiPublicMember *>(member)) {
184 if (publicMember->name && posIsInSource(pos, publicMember->expression)) {
185 value = lookupContext->context()->lookup(publicMember->name->asString());
186 if (const Interpreter::Reference *ref = value->asReference())
187 value = lookupContext->context()->lookupReference(ref);
188 color = textAt(qmlDocument,
189 publicMember->expression->firstSourceLocation(),
190 publicMember->expression->lastSourceLocation());
194 if (!color.isEmpty()) {
195 color.remove(QLatin1Char('\''));
196 color.remove(QLatin1Char('\"'));
197 color.remove(QLatin1Char(';'));
199 m_colorTip = QmlJS::toQColor(color);
200 if (m_colorTip.isValid()) {
208 void HoverHandler::handleOrdinaryMatch(const LookupContext::Ptr &lookupContext, AST::Node *node)
210 if (node && !(AST::cast<AST::StringLiteral *>(node) != 0 ||
211 AST::cast<AST::NumericLiteral *>(node) != 0)) {
212 const Interpreter::Value *value = lookupContext->evaluate(node);
213 prettyPrintTooltip(value, lookupContext->context());
217 void HoverHandler::reset()
219 m_colorTip = QColor();
222 void HoverHandler::operateTooltip(TextEditor::ITextEditor *editor, const QPoint &point)
224 if (toolTip().isEmpty())
225 TextEditor::ToolTip::instance()->hide();
227 if (m_colorTip.isValid()) {
228 TextEditor::ToolTip::instance()->show(point,
229 TextEditor::ColorContent(m_colorTip),
232 TextEditor::ToolTip::instance()->show(point,
233 TextEditor::TextContent(toolTip()),
239 void HoverHandler::prettyPrintTooltip(const QmlJS::Interpreter::Value *value,
240 const QmlJS::Interpreter::Context *context)
245 if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) {
246 Interpreter::PrototypeIterator iter(objectValue, context);
247 while (iter.hasNext()) {
248 const Interpreter::ObjectValue *prototype = iter.next();
249 const QString className = prototype->className();
251 if (! className.isEmpty()) {
252 setToolTip(className);
256 } else if (const Interpreter::QmlEnumValue *enumValue =
257 dynamic_cast<const Interpreter::QmlEnumValue *>(value)) {
258 setToolTip(enumValue->name());
261 if (toolTip().isEmpty()) {
262 QString typeId = context->engine()->typeId(value);
263 if (typeId != QLatin1String("undefined"))
268 // if node refers to a property, its name and defining object are returned - otherwise zero
269 static const Interpreter::ObjectValue *isMember(const LookupContext::Ptr &lookupContext,
270 AST::Node *node, QString *name)
272 const Interpreter::ObjectValue *owningObject = 0;
273 if (AST::IdentifierExpression *identExp = AST::cast<AST::IdentifierExpression *>(node)) {
276 *name = identExp->name->asString();
277 lookupContext->context()->lookup(*name, &owningObject);
278 } else if (AST::FieldMemberExpression *fme = AST::cast<AST::FieldMemberExpression *>(node)) {
279 if (!fme->base || !fme->name)
281 *name = fme->name->asString();
282 const Interpreter::Value *base = lookupContext->evaluate(fme->base);
285 owningObject = base->asObjectValue();
287 owningObject->lookupMember(*name, lookupContext->context(), &owningObject);
288 } else if (AST::UiQualifiedId *qid = AST::cast<AST::UiQualifiedId *>(node)) {
291 *name = qid->name->asString();
292 const Interpreter::Value *value = lookupContext->context()->lookup(*name, &owningObject);
293 for (AST::UiQualifiedId *it = qid->next; it; it = it->next) {
296 const Interpreter::ObjectValue *next = value->asObjectValue();
297 if (!next || !it->name)
299 *name = it->name->asString();
300 value = next->lookupMember(*name, lookupContext->context(), &owningObject);
306 TextEditor::HelpItem HoverHandler::qmlHelpItem(const LookupContext::Ptr &lookupContext,
307 AST::Node *node) const
310 if (const Interpreter::ObjectValue *scope = isMember(lookupContext, node, &name)) {
311 // maybe it's a type?
312 if (!name.isEmpty() && name.at(0).isUpper()) {
313 const QString maybeHelpId(QLatin1String("QML.") + name);
314 if (!Core::HelpManager::instance()->linksForIdentifier(maybeHelpId).isEmpty())
315 return TextEditor::HelpItem(maybeHelpId, name, TextEditor::HelpItem::QmlComponent);
318 // otherwise, it's probably a property
319 const Interpreter::ObjectValue *lastScope;
320 scope->lookupMember(name, lookupContext->context(), &lastScope);
321 Interpreter::PrototypeIterator iter(scope, lookupContext->context());
322 while (iter.hasNext()) {
323 const Interpreter::ObjectValue *cur = iter.next();
325 const QString className = cur->className();
326 if (!className.isEmpty()) {
327 const QString maybeHelpId(className + QLatin1String("::") + name);
328 if (!Core::HelpManager::instance()->linksForIdentifier(maybeHelpId).isEmpty())
329 return TextEditor::HelpItem(maybeHelpId, name, TextEditor::HelpItem::QmlProperty);
332 if (cur == lastScope)
337 return TextEditor::HelpItem();