OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / qmljseditor / qmljshoverhandler.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "qmljseditor.h"
34 #include "qmljseditoreditable.h"
35 #include "qmlexpressionundercursor.h"
36 #include "qmljshoverhandler.h"
37
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>
51
52 #include <QtCore/QList>
53
54 using namespace Core;
55 using namespace QmlJS;
56 using namespace QmlJSEditor;
57 using namespace QmlJSEditor::Internal;
58
59 namespace {
60
61     QString textAt(const Document::Ptr doc,
62                    const AST::SourceLocation &from,
63                    const AST::SourceLocation &to)
64     {
65         return doc->source().mid(from.offset, to.end() - from.begin());
66     }
67
68     AST::UiObjectInitializer *nodeInitializer(AST::Node *node)
69     {
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;
76         return initializer;
77     }
78
79     template <class T>
80     bool posIsInSource(const unsigned pos, T *node)
81     {
82         if (node &&
83             pos >= node->firstSourceLocation().begin() && pos < node->lastSourceLocation().end()) {
84             return true;
85         }
86         return false;
87     }
88 }
89
90 HoverHandler::HoverHandler(QObject *parent) : BaseHoverHandler(parent), m_modelManager(0)
91 {
92     m_modelManager =
93         ExtensionSystem::PluginManager::instance()->getObject<QmlJS::ModelManagerInterface>();
94 }
95
96 bool HoverHandler::acceptEditor(IEditor *editor)
97 {
98     QmlJSEditorEditable *qmlEditor = qobject_cast<QmlJSEditorEditable *>(editor);
99     if (qmlEditor)
100         return true;
101     return false;
102 }
103
104 void HoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos)
105 {
106     reset();
107
108     if (!m_modelManager)
109         return;
110
111     QmlJSEditor::QmlJSTextEditorWidget *qmlEditor = qobject_cast<QmlJSEditor::QmlJSTextEditorWidget *>(editor->widget());
112     if (!qmlEditor)
113         return;
114
115     if (matchDiagnosticMessage(qmlEditor, pos))
116         return;
117
118     const QmlJSEditor::SemanticInfo &semanticInfo = qmlEditor->semanticInfo();
119     if (! semanticInfo.isValid() || semanticInfo.revision() != qmlEditor->editorRevision())
120         return;
121
122     QList<AST::Node *> astPath = semanticInfo.astPath(pos);
123     if (astPath.isEmpty())
124         return;
125
126     const Document::Ptr qmlDocument = semanticInfo.document;
127     LookupContext::Ptr lookupContext = semanticInfo.lookupContext(astPath);
128
129     if (matchColorItem(lookupContext, qmlDocument, astPath, pos))
130         return;
131
132     AST::Node *node = semanticInfo.nodeUnderCursor(pos);
133     handleOrdinaryMatch(lookupContext, node);
134
135     TextEditor::HelpItem helpItem = qmlHelpItem(lookupContext, node);
136     if (!helpItem.helpId().isEmpty())
137         setLastHelpItemIdentified(helpItem);
138 }
139
140 bool HoverHandler::matchDiagnosticMessage(QmlJSEditor::QmlJSTextEditorWidget *qmlEditor, int pos)
141 {
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());
146             return true;
147         }
148     }
149     return false;
150 }
151
152 bool HoverHandler::matchColorItem(const LookupContext::Ptr &lookupContext,
153                                   const Document::Ptr &qmlDocument,
154                                   const QList<AST::Node *> &astPath,
155                                   unsigned pos)
156 {
157     AST::UiObjectInitializer *initializer = nodeInitializer(astPath.last());
158     if (!initializer)
159         return false;
160
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;
165             break;
166         }
167     }
168     if (!member)
169         return false;
170
171     QString color;
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());
180             }
181         }
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());
191         }
192     }
193
194     if (!color.isEmpty()) {
195         color.remove(QLatin1Char('\''));
196         color.remove(QLatin1Char('\"'));
197         color.remove(QLatin1Char(';'));
198
199         m_colorTip = QmlJS::toQColor(color);
200         if (m_colorTip.isValid()) {
201             setToolTip(color);
202             return true;
203         }
204     }
205     return false;
206 }
207
208 void HoverHandler::handleOrdinaryMatch(const LookupContext::Ptr &lookupContext, AST::Node *node)
209 {
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());
214     }
215 }
216
217 void HoverHandler::reset()
218 {
219     m_colorTip = QColor();
220 }
221
222 void HoverHandler::operateTooltip(TextEditor::ITextEditor *editor, const QPoint &point)
223 {
224     if (toolTip().isEmpty())
225         TextEditor::ToolTip::instance()->hide();
226     else {
227         if (m_colorTip.isValid()) {
228             TextEditor::ToolTip::instance()->show(point,
229                                                   TextEditor::ColorContent(m_colorTip),
230                                                   editor->widget());
231         } else {
232             TextEditor::ToolTip::instance()->show(point,
233                                                   TextEditor::TextContent(toolTip()),
234                                                   editor->widget());
235         }
236     }
237 }
238
239 void HoverHandler::prettyPrintTooltip(const QmlJS::Interpreter::Value *value,
240                                       const QmlJS::Interpreter::Context *context)
241 {
242     if (! value)
243         return;
244
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();
250
251             if (! className.isEmpty()) {
252                 setToolTip(className);
253                 break;
254             }
255         }
256     } else if (const Interpreter::QmlEnumValue *enumValue =
257                dynamic_cast<const Interpreter::QmlEnumValue *>(value)) {
258         setToolTip(enumValue->name());
259     }
260
261     if (toolTip().isEmpty()) {
262         QString typeId = context->engine()->typeId(value);
263         if (typeId != QLatin1String("undefined"))
264             setToolTip(typeId);
265     }
266 }
267
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)
271 {
272     const Interpreter::ObjectValue *owningObject = 0;
273     if (AST::IdentifierExpression *identExp = AST::cast<AST::IdentifierExpression *>(node)) {
274         if (!identExp->name)
275             return 0;
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)
280             return 0;
281         *name = fme->name->asString();
282         const Interpreter::Value *base = lookupContext->evaluate(fme->base);
283         if (!base)
284             return 0;
285         owningObject = base->asObjectValue();
286         if (owningObject)
287             owningObject->lookupMember(*name, lookupContext->context(), &owningObject);
288     } else if (AST::UiQualifiedId *qid = AST::cast<AST::UiQualifiedId *>(node)) {
289         if (!qid->name)
290             return 0;
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) {
294             if (!value)
295                 return 0;
296             const Interpreter::ObjectValue *next = value->asObjectValue();
297             if (!next || !it->name)
298                 return 0;
299             *name = it->name->asString();
300             value = next->lookupMember(*name, lookupContext->context(), &owningObject);
301         }
302     }
303     return owningObject;
304 }
305
306 TextEditor::HelpItem HoverHandler::qmlHelpItem(const LookupContext::Ptr &lookupContext,
307                                                AST::Node *node) const
308 {
309     QString name;
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);
316         }
317
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();
324
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);
330             }
331
332             if (cur == lastScope)
333                 break;
334         }
335     }
336
337     return TextEditor::HelpItem();
338 }