1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "qmloutlinemodel.h"
27 #include "qmljseditor.h"
28 
29 #include <qmljs/parser/qmljsastvisitor_p.h>
30 #include <qmljs/qmljscontext.h>
31 #include <qmljs/qmljsscopechain.h>
32 #include <qmljs/qmljsmodelmanagerinterface.h>
33 #include <qmljs/qmljsrewriter.h>
34 #include <qmljs/qmljsvalueowner.h>
35 #include <qmljstools/qmljsrefactoringchanges.h>
36 
37 #include <utils/qtcassert.h>
38 #include <utils/dropsupport.h>
39 
40 #include <coreplugin/icore.h>
41 #include <QDebug>
42 #include <QTime>
43 #include <QMimeData>
44 #include <typeinfo>
45 
46 using namespace QmlJS;
47 using namespace QmlJSEditor::Internal;
48 using namespace QmlJSTools;
49 
50 enum {
51     debug = false
52 };
53 
54 static const char INTERNAL_MIMETYPE[] = "application/x-qtcreator-qmloutlinemodel";
55 
56 namespace QmlJSEditor {
57 namespace Internal {
58 
QmlOutlineItem(QmlOutlineModel * model)59 QmlOutlineItem::QmlOutlineItem(QmlOutlineModel *model) :
60     m_outlineModel(model)
61 {
62 }
63 
data(int role) const64 QVariant QmlOutlineItem::data(int role) const
65 {
66     if (role == Qt::ToolTipRole) {
67         SourceLocation location = m_outlineModel->sourceLocation(index());
68         AST::UiQualifiedId *uiQualifiedId = m_outlineModel->idNode(index());
69         if (!uiQualifiedId || !location.isValid() || !m_outlineModel->m_semanticInfo.isValid())
70             return QVariant();
71 
72         QList<AST::Node *> astPath = m_outlineModel->m_semanticInfo.rangePath(location.begin());
73         ScopeChain scopeChain = m_outlineModel->m_semanticInfo.scopeChain(astPath);
74         const Value *value = scopeChain.evaluate(uiQualifiedId);
75 
76         return prettyPrint(value, scopeChain.context());
77     }
78 
79     if (role == Qt::DecorationRole)
80         return m_outlineModel->icon(index());
81 
82     return QStandardItem::data(role);
83 }
84 
type() const85 int QmlOutlineItem::type() const
86 {
87     return UserType;
88 }
89 
setItemData(const QMap<int,QVariant> & roles)90 void QmlOutlineItem::setItemData(const QMap<int, QVariant> &roles)
91 {
92     QMap<int,QVariant>::const_iterator iter(roles.constBegin());
93     while (iter != roles.constEnd()) {
94         setData(iter.value(), iter.key());
95         ++iter;
96     }
97 }
98 
prettyPrint(const Value * value,const ContextPtr & context) const99 QString QmlOutlineItem::prettyPrint(const Value *value, const ContextPtr &context) const
100 {
101     if (! value)
102         return QString();
103 
104     if (const ObjectValue *objectValue = value->asObjectValue()) {
105         const QString className = objectValue->className();
106         if (!className.isEmpty())
107             return className;
108     }
109 
110     const QString typeId = context->valueOwner()->typeId(value);
111     if (typeId == QLatin1String("undefined"))
112         return QString();
113 
114     return typeId;
115 }
116 
117 /**
118   Returns mapping of every UiObjectMember object to it's direct UiObjectMember parent object.
119   */
120 class ObjectMemberParentVisitor : public AST::Visitor
121 {
122 public:
operator ()(Document::Ptr doc)123     QHash<AST::Node *,AST::UiObjectMember *> operator()(Document::Ptr doc) {
124         parent.clear();
125         if (doc && doc->ast())
126             doc->ast()->accept(this);
127         return parent;
128     }
129 
130 private:
131     QHash<AST::Node *, AST::UiObjectMember *> parent;
132     QList<AST::UiObjectMember *> stack;
133 
preVisit(AST::Node * node)134     bool preVisit(AST::Node *node) override
135     {
136         if (AST::UiObjectMember *objMember = node->uiObjectMemberCast())
137             stack.append(objMember);
138         return true;
139     }
140 
postVisit(AST::Node * node)141     void postVisit(AST::Node *node) override
142     {
143         if (AST::UiObjectMember *objMember = node->uiObjectMemberCast()) {
144             stack.removeLast();
145             if (!stack.isEmpty())
146                 parent.insert(objMember, stack.last());
147         } else if (AST::FunctionExpression *funcMember = node->asFunctionDefinition()) {
148             if (!stack.isEmpty())
149                 parent.insert(funcMember, stack.last());
150         }
151     }
152 
throwRecursionDepthError()153     void throwRecursionDepthError() override
154     {
155         qWarning("Warning: Hit maximum recursion depth while visiting AST in ObjectMemberParentVisitor");
156     }
157 };
158 
159 
160 class QmlOutlineModelSync : protected AST::Visitor
161 {
162 public:
QmlOutlineModelSync(QmlOutlineModel * model)163     QmlOutlineModelSync(QmlOutlineModel *model) :
164         m_model(model),
165         indent(0)
166     {
167     }
168 
operator ()(Document::Ptr doc)169     void operator()(Document::Ptr doc)
170     {
171         m_nodeToIndex.clear();
172 
173         if (debug)
174             qDebug() << "QmlOutlineModel ------";
175         if (doc && doc->ast())
176             doc->ast()->accept(this);
177     }
178 
179 private:
preVisit(AST::Node * node)180     bool preVisit(AST::Node *node) override
181     {
182         if (!node)
183             return false;
184         if (debug)
185             qDebug() << "QmlOutlineModel -" << QByteArray(indent++, '-').constData() << node << typeid(*node).name();
186         return true;
187     }
188 
postVisit(AST::Node *)189     void postVisit(AST::Node *) override
190     {
191         indent--;
192     }
193 
194     using ElementType = QPair<QString,QString>;
visit(AST::UiObjectDefinition * objDef)195     bool visit(AST::UiObjectDefinition *objDef) override
196     {
197         QModelIndex index = m_model->enterObjectDefinition(objDef);
198         m_nodeToIndex.insert(objDef, index);
199         return true;
200     }
201 
endVisit(AST::UiObjectDefinition *)202     void endVisit(AST::UiObjectDefinition * /*objDef*/) override
203     {
204         m_model->leaveObjectDefiniton();
205     }
206 
visit(AST::UiObjectBinding * objBinding)207     bool visit(AST::UiObjectBinding *objBinding) override
208     {
209         QModelIndex index = m_model->enterObjectBinding(objBinding);
210         m_nodeToIndex.insert(objBinding, index);
211         return true;
212     }
213 
endVisit(AST::UiObjectBinding *)214     void endVisit(AST::UiObjectBinding * /*objBinding*/) override
215     {
216         m_model->leaveObjectBinding();
217     }
218 
visit(AST::UiArrayBinding * arrayBinding)219     bool visit(AST::UiArrayBinding *arrayBinding) override
220     {
221         QModelIndex index = m_model->enterArrayBinding(arrayBinding);
222         m_nodeToIndex.insert(arrayBinding, index);
223 
224         return true;
225     }
226 
endVisit(AST::UiArrayBinding *)227     void endVisit(AST::UiArrayBinding * /*arrayBinding*/) override
228     {
229         m_model->leaveArrayBinding();
230     }
231 
visit(AST::UiScriptBinding * scriptBinding)232     bool visit(AST::UiScriptBinding *scriptBinding) override
233     {
234         QModelIndex index = m_model->enterScriptBinding(scriptBinding);
235         m_nodeToIndex.insert(scriptBinding, index);
236 
237         return true;
238     }
239 
endVisit(AST::UiScriptBinding *)240     void endVisit(AST::UiScriptBinding * /*scriptBinding*/) override
241     {
242         m_model->leaveScriptBinding();
243     }
244 
visit(AST::UiPublicMember * publicMember)245     bool visit(AST::UiPublicMember *publicMember) override
246     {
247         QModelIndex index = m_model->enterPublicMember(publicMember);
248         m_nodeToIndex.insert(publicMember, index);
249 
250         return true;
251     }
252 
endVisit(AST::UiPublicMember *)253     void endVisit(AST::UiPublicMember * /*publicMember*/) override
254     {
255         m_model->leavePublicMember();
256     }
257 
visit(AST::FunctionDeclaration * functionDeclaration)258     bool visit(AST::FunctionDeclaration *functionDeclaration) override
259     {
260         QModelIndex index = m_model->enterFunctionDeclaration(functionDeclaration);
261         m_nodeToIndex.insert(functionDeclaration, index);
262 
263         return true;
264     }
265 
endVisit(AST::FunctionDeclaration *)266     void endVisit(AST::FunctionDeclaration * /*functionDeclaration*/) override
267     {
268         m_model->leaveFunctionDeclaration();
269     }
270 
visit(AST::BinaryExpression * binExp)271     bool visit(AST::BinaryExpression *binExp) override
272     {
273         auto lhsIdent = AST::cast<const AST::IdentifierExpression *>(binExp->left);
274         auto rhsObjLit = AST::cast<AST::ObjectPattern *>(binExp->right);
275 
276         if (lhsIdent && rhsObjLit && (lhsIdent->name == QLatin1String("testcase"))
277             && (binExp->op == QSOperator::Assign)) {
278             QModelIndex index = m_model->enterTestCase(rhsObjLit);
279             m_nodeToIndex.insert(rhsObjLit, index);
280 
281             if (AST::PatternPropertyList *properties = rhsObjLit->properties)
282                 visitProperties(properties);
283 
284             m_model->leaveTestCase();
285             return true;
286         }
287 
288         // Collect method assignments for prototypes and objects and show as functions
289         auto lhsField = AST::cast<AST::FieldMemberExpression *>(binExp->left);
290         auto rhsFuncExpr = AST::cast<AST::FunctionExpression *>(binExp->right);
291 
292         if (lhsField && rhsFuncExpr && rhsFuncExpr->body && (binExp->op == QSOperator::Assign)) {
293             QModelIndex index = m_model->enterFieldMemberExpression(lhsField, rhsFuncExpr);
294             m_nodeToIndex.insert(lhsField, index);
295             m_model->leaveFieldMemberExpression();
296         }
297 
298         return true;
299     }
300 
visitProperties(AST::PatternPropertyList * properties)301     void visitProperties(AST::PatternPropertyList *properties)
302     {
303         while (properties) {
304             QModelIndex index = m_model->enterTestCaseProperties(properties);
305             m_nodeToIndex.insert(properties, index);
306             if (auto assignment = AST::cast<const AST::PatternProperty *>(properties->property))
307                 if (auto objLiteral = AST::cast<const AST::ObjectPattern *>(assignment->initializer))
308                     visitProperties(objLiteral->properties);
309 
310             m_model->leaveTestCaseProperties();
311             properties = properties->next;
312         }
313     }
314 
throwRecursionDepthError()315     void throwRecursionDepthError() override
316     {
317         qWarning("Warning: Hit maximum recursion limit visiting AST in QmlOutlineModelSync");
318     }
319 
320     QmlOutlineModel *m_model;
321 
322     QHash<AST::Node*, QModelIndex> m_nodeToIndex;
323     int indent;
324 };
325 
QmlOutlineModel(QmlJSEditorDocument * document)326 QmlOutlineModel::QmlOutlineModel(QmlJSEditorDocument *document) :
327     QStandardItemModel(document),
328     m_editorDocument(document)
329 {
330     m_icons = Icons::instance();
331     Icons::instance()->setIconFilesPath(Core::ICore::resourcePath("qmlicons").toString());
332 
333     setItemPrototype(new QmlOutlineItem(this));
334 }
335 
mimeTypes() const336 QStringList QmlOutlineModel::mimeTypes() const
337 {
338     QStringList types;
339     types << QLatin1String(INTERNAL_MIMETYPE);
340     types << Utils::DropSupport::mimeTypesForFilePaths();
341     return types;
342 }
343 
344 
mimeData(const QModelIndexList & indexes) const345 QMimeData *QmlOutlineModel::mimeData(const QModelIndexList &indexes) const
346 {
347     if (indexes.isEmpty())
348         return nullptr;
349     auto data = new Utils::DropMimeData;
350     data->setOverrideFileDropAction(Qt::CopyAction);
351     QByteArray encoded;
352     QDataStream stream(&encoded, QIODevice::WriteOnly);
353     stream << indexes.size();
354 
355     for (const auto &index : indexes) {
356         SourceLocation location = sourceLocation(index);
357         data->addFile(m_editorDocument->filePath().toString(), location.startLine,
358                       location.startColumn - 1 /*editors have 0-based column*/);
359 
360         QList<int> rowPath;
361         for (QModelIndex i = index; i.isValid(); i = i.parent()) {
362             rowPath.prepend(i.row());
363         }
364 
365         stream << rowPath;
366     }
367     data->setData(QLatin1String(INTERNAL_MIMETYPE), encoded);
368     return data;
369 }
370 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int,const QModelIndex & parent)371 bool QmlOutlineModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent)
372 {
373     if (debug)
374         qDebug() << __FUNCTION__ << row << parent;
375 
376     // check if the action is supported
377     if (!data || !(action == Qt::CopyAction || action == Qt::MoveAction))
378         return false;
379 
380     // We cannot reparent outside of the root item
381     if (!parent.isValid())
382         return false;
383 
384     // check if the format is supported
385     QStringList types = mimeTypes();
386     if (types.isEmpty())
387         return false;
388     QString format = types.at(0);
389     if (!data->hasFormat(format))
390         return false;
391 
392     // decode and insert
393     QByteArray encoded = data->data(format);
394     QDataStream stream(&encoded, QIODevice::ReadOnly);
395     int indexSize;
396     stream >> indexSize;
397     QList<QmlOutlineItem*> itemsToMove;
398     for (int i = 0; i < indexSize; ++i) {
399         QList<int> rowPath;
400         stream >> rowPath;
401 
402         QModelIndex index;
403         foreach (int row, rowPath) {
404             index = this->index(row, 0, index);
405             if (!index.isValid())
406                 continue;
407         }
408 
409         itemsToMove << static_cast<QmlOutlineItem*>(itemFromIndex(index));
410     }
411 
412     auto targetItem = static_cast<QmlOutlineItem*>(itemFromIndex(parent));
413     reparentNodes(targetItem, row, itemsToMove);
414 
415     // Prevent view from calling removeRow() on it's own
416     return false;
417 }
418 
flags(const QModelIndex & index) const419 Qt::ItemFlags QmlOutlineModel::flags(const QModelIndex &index) const
420 {
421     if (!index.isValid())
422         return QStandardItemModel::flags(index);
423 
424     Qt::ItemFlags flags = Qt::ItemIsSelectable|Qt::ItemIsEnabled;
425 
426     // only allow drag&drop if we're in sync
427     if (m_semanticInfo.isValid()
428             && !m_editorDocument->isSemanticInfoOutdated()) {
429         if (index.parent().isValid())
430             flags |= Qt::ItemIsDragEnabled;
431         if (index.data(ItemTypeRole) != NonElementBindingType)
432             flags |= Qt::ItemIsDropEnabled;
433     }
434     return flags;
435 }
436 
supportedDragActions() const437 Qt::DropActions QmlOutlineModel::supportedDragActions() const
438 {
439     // copy action used for dragging onto editor splits
440     return Qt::MoveAction | Qt::CopyAction;
441 }
442 
supportedDropActions() const443 Qt::DropActions QmlOutlineModel::supportedDropActions() const
444 {
445     return Qt::MoveAction;
446 }
447 
448 
document() const449 Document::Ptr QmlOutlineModel::document() const
450 {
451     return m_semanticInfo.document;
452 }
453 
update(const SemanticInfo & semanticInfo)454 void QmlOutlineModel::update(const SemanticInfo &semanticInfo)
455 {
456     m_semanticInfo = semanticInfo;
457     if (! m_semanticInfo.isValid())
458         return;
459 
460     m_treePos.clear();
461     m_treePos.append(0);
462     m_currentItem = invisibleRootItem();
463 
464     // resetModel() actually gives better average performance
465     // then the incremental updates.
466     beginResetModel();
467 
468     m_typeToIcon.clear();
469     m_itemToNode.clear();
470     m_itemToIdNode.clear();
471     m_itemToIcon.clear();
472 
473 
474     QmlOutlineModelSync syncModel(this);
475     syncModel(m_semanticInfo.document);
476 
477     endResetModel();
478 
479     emit updated();
480 }
481 
enterObjectDefinition(AST::UiObjectDefinition * objDef)482 QModelIndex QmlOutlineModel::enterObjectDefinition(AST::UiObjectDefinition *objDef)
483 {
484     const QString typeName = asString(objDef->qualifiedTypeNameId);
485 
486     QMap<int, QVariant> data;
487     AST::UiQualifiedId *idNode = nullptr;
488     QIcon icon;
489 
490     data.insert(Qt::DisplayRole, typeName);
491 
492     if (typeName.at(0).isUpper()) {
493         data.insert(ItemTypeRole, ElementType);
494         data.insert(AnnotationRole, getAnnotation(objDef->initializer));
495         idNode = objDef->qualifiedTypeNameId;
496         if (!m_typeToIcon.contains(typeName))
497             m_typeToIcon.insert(typeName, getIcon(objDef->qualifiedTypeNameId));
498         icon = m_typeToIcon.value(typeName);
499     } else {
500         // it's a grouped propery like 'anchors'
501         data.insert(ItemTypeRole, NonElementBindingType);
502         data.insert(AnnotationRole, QString()); // clear possible former annotation
503         icon = Icons::scriptBindingIcon();
504     }
505 
506     QmlOutlineItem *item = enterNode(data, objDef, idNode, icon);
507 
508     return item->index();
509 }
510 
leaveObjectDefiniton()511 void QmlOutlineModel::leaveObjectDefiniton()
512 {
513     leaveNode();
514 }
515 
enterObjectBinding(AST::UiObjectBinding * objBinding)516 QModelIndex QmlOutlineModel::enterObjectBinding(AST::UiObjectBinding *objBinding)
517 {
518     QMap<int, QVariant> bindingData;
519 
520     bindingData.insert(Qt::DisplayRole, asString(objBinding->qualifiedId));
521     bindingData.insert(ItemTypeRole, ElementBindingType);
522     bindingData.insert(AnnotationRole, QString()); // clear possible former annotation
523 
524     QmlOutlineItem *bindingItem = enterNode(bindingData, objBinding, objBinding->qualifiedId, Icons::scriptBindingIcon());
525 
526     const QString typeName = asString(objBinding->qualifiedTypeNameId);
527     if (!m_typeToIcon.contains(typeName))
528         m_typeToIcon.insert(typeName, getIcon(objBinding->qualifiedTypeNameId));
529 
530     QMap<int, QVariant> objectData;
531     objectData.insert(Qt::DisplayRole, typeName);
532     objectData.insert(AnnotationRole, getAnnotation(objBinding->initializer));
533     objectData.insert(ItemTypeRole, ElementType);
534 
535     enterNode(objectData, objBinding, objBinding->qualifiedTypeNameId, m_typeToIcon.value(typeName));
536 
537     return bindingItem->index();
538 }
539 
leaveObjectBinding()540 void QmlOutlineModel::leaveObjectBinding()
541 {
542     leaveNode();
543     leaveNode();
544 }
545 
enterArrayBinding(AST::UiArrayBinding * arrayBinding)546 QModelIndex QmlOutlineModel::enterArrayBinding(AST::UiArrayBinding *arrayBinding)
547 {
548     QMap<int, QVariant> bindingData;
549 
550     bindingData.insert(Qt::DisplayRole, asString(arrayBinding->qualifiedId));
551     bindingData.insert(ItemTypeRole, ElementBindingType);
552     bindingData.insert(AnnotationRole, QString()); // clear possible former annotation
553 
554     QmlOutlineItem *item = enterNode(bindingData, arrayBinding, arrayBinding->qualifiedId, Icons::scriptBindingIcon());
555 
556     return item->index();
557 }
558 
leaveArrayBinding()559 void QmlOutlineModel::leaveArrayBinding()
560 {
561     leaveNode();
562 }
563 
enterScriptBinding(AST::UiScriptBinding * scriptBinding)564 QModelIndex QmlOutlineModel::enterScriptBinding(AST::UiScriptBinding *scriptBinding)
565 {
566     QMap<int, QVariant> objectData;
567 
568     objectData.insert(Qt::DisplayRole, asString(scriptBinding->qualifiedId));
569     objectData.insert(AnnotationRole, getAnnotation(scriptBinding->statement));
570     objectData.insert(ItemTypeRole, NonElementBindingType);
571 
572     QmlOutlineItem *item = enterNode(objectData, scriptBinding, scriptBinding->qualifiedId, Icons::scriptBindingIcon());
573 
574     return item->index();
575 }
576 
leaveScriptBinding()577 void QmlOutlineModel::leaveScriptBinding()
578 {
579     leaveNode();
580 }
581 
enterPublicMember(AST::UiPublicMember * publicMember)582 QModelIndex QmlOutlineModel::enterPublicMember(AST::UiPublicMember *publicMember)
583 {
584     QMap<int, QVariant> objectData;
585 
586     if (!publicMember->name.isEmpty())
587         objectData.insert(Qt::DisplayRole, publicMember->name.toString());
588     objectData.insert(AnnotationRole, getAnnotation(publicMember->statement));
589     objectData.insert(ItemTypeRole, NonElementBindingType);
590 
591     QmlOutlineItem *item = enterNode(objectData, publicMember, nullptr, Icons::publicMemberIcon());
592 
593     return item->index();
594 }
595 
leavePublicMember()596 void QmlOutlineModel::leavePublicMember()
597 {
598     leaveNode();
599 }
600 
functionDisplayName(QStringView name,AST::FormalParameterList * formals)601 static QString functionDisplayName(QStringView name, AST::FormalParameterList *formals)
602 {
603     QString display;
604 
605     if (!name.isEmpty())
606         display += name.toString() + QLatin1Char('(');
607     for (AST::FormalParameterList *param = formals; param; param = param->next) {
608         display += param->element->bindingIdentifier.toString();
609         if (param->next)
610             display += QLatin1String(", ");
611     }
612     if (!name.isEmpty())
613         display += QLatin1Char(')');
614 
615     return display;
616 }
617 
enterFunctionDeclaration(AST::FunctionDeclaration * functionDeclaration)618 QModelIndex QmlOutlineModel::enterFunctionDeclaration(AST::FunctionDeclaration *functionDeclaration)
619 {
620     QMap<int, QVariant> objectData;
621 
622     objectData.insert(Qt::DisplayRole, functionDisplayName(functionDeclaration->name,
623                                                            functionDeclaration->formals));
624     objectData.insert(ItemTypeRole, ElementBindingType);
625     objectData.insert(AnnotationRole, QString()); // clear possible former annotation
626 
627     QmlOutlineItem *item = enterNode(objectData, functionDeclaration, nullptr,
628                                      Icons::functionDeclarationIcon());
629 
630     return item->index();
631 }
632 
leaveFunctionDeclaration()633 void QmlOutlineModel::leaveFunctionDeclaration()
634 {
635     leaveNode();
636 }
637 
enterFieldMemberExpression(AST::FieldMemberExpression * expression,AST::FunctionExpression * functionExpression)638 QModelIndex QmlOutlineModel::enterFieldMemberExpression(AST::FieldMemberExpression *expression,
639                                                         AST::FunctionExpression *functionExpression)
640 {
641     QMap<int, QVariant> objectData;
642 
643     QString display = functionDisplayName(expression->name, functionExpression->formals);
644     while (expression) {
645         if (auto field = AST::cast<AST::FieldMemberExpression *>(expression->base)) {
646             display.prepend(field->name.toString() + QLatin1Char('.'));
647             expression = field;
648         } else {
649             if (auto ident = AST::cast<AST::IdentifierExpression *>(expression->base))
650                 display.prepend(ident->name.toString() + QLatin1Char('.'));
651             break;
652         }
653     }
654 
655     objectData.insert(Qt::DisplayRole, display);
656     objectData.insert(ItemTypeRole, ElementBindingType);
657     objectData.insert(AnnotationRole, QString()); // clear possible former annotation
658 
659     QmlOutlineItem *item = enterNode(objectData, expression, nullptr,
660                                      Icons::functionDeclarationIcon());
661 
662     return item->index();
663 }
664 
leaveFieldMemberExpression()665 void QmlOutlineModel::leaveFieldMemberExpression()
666 {
667     leaveNode();
668 }
669 
enterTestCase(AST::ObjectPattern * objectLiteral)670 QModelIndex QmlOutlineModel::enterTestCase(AST::ObjectPattern *objectLiteral)
671 {
672     QMap<int, QVariant> objectData;
673 
674     objectData.insert(Qt::DisplayRole, QLatin1String("testcase"));
675     objectData.insert(ItemTypeRole, ElementBindingType);
676     objectData.insert(AnnotationRole, QString()); // clear possible former annotation
677 
678     QmlOutlineItem *item = enterNode(objectData, objectLiteral, nullptr,
679                                      Icons::objectDefinitionIcon());
680 
681     return item->index();
682 }
683 
leaveTestCase()684 void QmlOutlineModel::leaveTestCase()
685 {
686     leaveNode();
687 }
688 
enterTestCaseProperties(AST::PatternPropertyList * propertyAssignmentList)689 QModelIndex QmlOutlineModel::enterTestCaseProperties(AST::PatternPropertyList *propertyAssignmentList)
690 {
691     QMap<int, QVariant> objectData;
692     if (auto assignment = AST::cast<AST::PatternProperty *>(
693                 propertyAssignmentList->property)) {
694         if (auto propertyName = AST::cast<const AST::IdentifierPropertyName *>(assignment->name)) {
695             objectData.insert(Qt::DisplayRole, propertyName->id.toString());
696             objectData.insert(ItemTypeRole, ElementBindingType);
697             objectData.insert(AnnotationRole, QString()); // clear possible former annotation
698             QmlOutlineItem *item;
699             if (assignment->initializer->kind == AST::Node::Kind_FunctionExpression)
700                 item = enterNode(objectData, assignment, nullptr, Icons::functionDeclarationIcon());
701             else if (assignment->initializer->kind == AST::Node::Kind_ObjectPattern)
702                 item = enterNode(objectData, assignment, nullptr, Icons::objectDefinitionIcon());
703             else
704                 item = enterNode(objectData, assignment, nullptr, Icons::scriptBindingIcon());
705 
706             return item->index();
707         }
708     }
709     if (auto getterSetter = AST::cast<AST::PatternProperty *>(
710                 propertyAssignmentList->property)) {
711         if (auto propertyName = AST::cast<const AST::IdentifierPropertyName *>(getterSetter->name)) {
712             objectData.insert(Qt::DisplayRole, propertyName->id.toString());
713             objectData.insert(ItemTypeRole, ElementBindingType);
714             objectData.insert(AnnotationRole, QString()); // clear possible former annotation
715             QmlOutlineItem *item;
716             item = enterNode(objectData, getterSetter, nullptr, Icons::functionDeclarationIcon());
717 
718             return item->index();
719 
720         }
721     }
722     return QModelIndex();
723 }
724 
leaveTestCaseProperties()725 void QmlOutlineModel::leaveTestCaseProperties()
726 {
727     leaveNode();
728 }
729 
nodeForIndex(const QModelIndex & index) const730 AST::Node *QmlOutlineModel::nodeForIndex(const QModelIndex &index) const
731 {
732     QTC_ASSERT(index.isValid() && (index.model() == this), return nullptr);
733     if (index.isValid()) {
734         auto item = static_cast<QmlOutlineItem*>(itemFromIndex(index));
735         QTC_ASSERT(item, return nullptr);
736         QTC_ASSERT(m_itemToNode.contains(item), return nullptr);
737         return m_itemToNode.value(item);
738     }
739     return nullptr;
740 }
741 
sourceLocation(const QModelIndex & index) const742 SourceLocation QmlOutlineModel::sourceLocation(const QModelIndex &index) const
743 {
744     SourceLocation location;
745     QTC_ASSERT(index.isValid() && (index.model() == this), return location);
746     AST::Node *node = nodeForIndex(index);
747     if (node) {
748         if (AST::UiObjectMember *member = node->uiObjectMemberCast())
749             location = getLocation(member);
750         else if (AST::ExpressionNode *expression = node->expressionCast())
751             location = getLocation(expression);
752         else if (auto propertyAssignmentList = AST::cast<AST::PatternPropertyList *>(node))
753             location = getLocation(propertyAssignmentList);
754     }
755     return location;
756 }
757 
idNode(const QModelIndex & index) const758 AST::UiQualifiedId *QmlOutlineModel::idNode(const QModelIndex &index) const
759 {
760     QTC_ASSERT(index.isValid() && (index.model() == this), return nullptr);
761     auto item = static_cast<QmlOutlineItem*>(itemFromIndex(index));
762     return m_itemToIdNode.value(item);
763 }
764 
icon(const QModelIndex & index) const765 QIcon QmlOutlineModel::icon(const QModelIndex &index) const
766 {
767     QTC_ASSERT(index.isValid() && (index.model() == this), return QIcon());
768     auto item = static_cast<QmlOutlineItem*>(itemFromIndex(index));
769     return m_itemToIcon.value(item);
770 }
771 
enterNode(QMap<int,QVariant> data,AST::Node * node,AST::UiQualifiedId * idNode,const QIcon & icon)772 QmlOutlineItem *QmlOutlineModel::enterNode(QMap<int, QVariant> data, AST::Node *node, AST::UiQualifiedId *idNode, const QIcon &icon)
773 {
774     int siblingIndex = m_treePos.last();
775     QmlOutlineItem *newItem = nullptr;
776     if (siblingIndex == 0) {
777         // first child
778         if (!m_currentItem->hasChildren()) {
779             if (debug)
780                 qDebug() << "QmlOutlineModel - Adding" << "element to" << m_currentItem->text();
781 
782             newItem = new QmlOutlineItem(this);
783         } else {
784             m_currentItem = m_currentItem->child(0);
785         }
786     } else {
787         // sibling
788         if (m_currentItem->rowCount() <= siblingIndex) {
789             if (debug)
790                 qDebug() << "QmlOutlineModel - Adding" << "element to" << m_currentItem->text();
791 
792             newItem = new QmlOutlineItem(this);
793         } else {
794             m_currentItem = m_currentItem->child(siblingIndex);
795         }
796     }
797 
798     QmlOutlineItem *item = newItem ? newItem : static_cast<QmlOutlineItem*>(m_currentItem);
799     m_itemToNode.insert(item, node);
800     m_itemToIdNode.insert(item, idNode);
801     m_itemToIcon.insert(item, icon);
802 
803     if (newItem) {
804         m_currentItem->appendRow(newItem);
805         m_currentItem = newItem;
806     }
807 
808     setItemData(m_currentItem->index(), data);
809 
810     m_treePos.append(0);
811 
812     return item;
813 }
814 
leaveNode()815 void QmlOutlineModel::leaveNode()
816 {
817     int lastIndex = m_treePos.takeLast();
818 
819 
820     if (lastIndex > 0) {
821         // element has children
822         if (lastIndex < m_currentItem->rowCount()) {
823             if (debug)
824                 qDebug() << "QmlOutlineModel - removeRows from " << m_currentItem->text() << lastIndex << m_currentItem->rowCount() - lastIndex;
825             m_currentItem->removeRows(lastIndex, m_currentItem->rowCount() - lastIndex);
826         }
827         m_currentItem = parentItem();
828     } else {
829         if (m_currentItem->hasChildren()) {
830             if (debug)
831                 qDebug() << "QmlOutlineModel - removeRows from " << m_currentItem->text() << 0 << m_currentItem->rowCount();
832             m_currentItem->removeRows(0, m_currentItem->rowCount());
833         }
834         m_currentItem = parentItem();
835     }
836 
837 
838     m_treePos.last()++;
839 }
840 
reparentNodes(QmlOutlineItem * targetItem,int row,QList<QmlOutlineItem * > itemsToMove)841 void QmlOutlineModel::reparentNodes(QmlOutlineItem *targetItem, int row, QList<QmlOutlineItem*> itemsToMove)
842 {
843     Utils::ChangeSet changeSet;
844 
845     AST::UiObjectMember *targetObjectMember = m_itemToNode.value(targetItem)->uiObjectMemberCast();
846     if (!targetObjectMember)
847         return;
848 
849     QList<Utils::ChangeSet::Range> changedRanges;
850 
851     for (auto outlineItem : itemsToMove) {
852         AST::UiObjectMember *sourceObjectMember = m_itemToNode.value(outlineItem)->uiObjectMemberCast();
853         AST::FunctionExpression *functionMember = nullptr;
854         if (!sourceObjectMember) {
855             functionMember = m_itemToNode.value(outlineItem)->asFunctionDefinition();
856             if (!functionMember)
857                 return;
858         }
859 
860         m_itemToNode.value(outlineItem)->asFunctionDefinition();
861         bool insertionOrderSpecified = true;
862         AST::UiObjectMember *memberToInsertAfter = nullptr;
863         {
864             if (row == -1) {
865                 insertionOrderSpecified = false;
866             } else if (row > 0) {
867                 auto outlineItem = static_cast<QmlOutlineItem*>(targetItem->child(row - 1));
868                 memberToInsertAfter = m_itemToNode.value(outlineItem)->uiObjectMemberCast();
869             }
870         }
871 
872         Utils::ChangeSet::Range range;
873         if (sourceObjectMember)
874             moveObjectMember(sourceObjectMember, targetObjectMember, insertionOrderSpecified,
875                              memberToInsertAfter, &changeSet, &range);
876         else if (functionMember)
877             moveObjectMember(functionMember, targetObjectMember, insertionOrderSpecified,
878                              memberToInsertAfter, &changeSet, &range);
879         changedRanges << range;
880     }
881 
882     QmlJSRefactoringChanges refactoring(ModelManagerInterface::instance(), m_semanticInfo.snapshot);
883     TextEditor::RefactoringFilePtr file = refactoring.file(
884         Utils::FilePath::fromString(m_semanticInfo.document->fileName()));
885     file->setChangeSet(changeSet);
886     foreach (const Utils::ChangeSet::Range &range, changedRanges) {
887         file->appendIndentRange(range);
888     }
889     file->apply();
890 }
891 
moveObjectMember(AST::Node * toMove,AST::UiObjectMember * newParent,bool insertionOrderSpecified,AST::UiObjectMember * insertAfter,Utils::ChangeSet * changeSet,Utils::ChangeSet::Range * addedRange)892 void QmlOutlineModel::moveObjectMember(AST::Node *toMove,
893                                        AST::UiObjectMember *newParent,
894                                        bool insertionOrderSpecified,
895                                        AST::UiObjectMember *insertAfter,
896                                        Utils::ChangeSet *changeSet,
897                                        Utils::ChangeSet::Range *addedRange)
898 {
899     Q_ASSERT(toMove);
900     Q_ASSERT(newParent);
901     Q_ASSERT(changeSet);
902 
903     QHash<AST::Node *, AST::UiObjectMember *> parentMembers;
904     {
905         ObjectMemberParentVisitor visitor;
906         parentMembers = visitor(m_semanticInfo.document);
907     }
908 
909     AST::UiObjectMember *oldParent = parentMembers.value(toMove);
910     Q_ASSERT(oldParent);
911 
912     // make sure that target parent is actually a direct ancestor of target sibling
913     if (insertAfter) {
914         auto objMember = parentMembers.value(insertAfter);
915         Q_ASSERT(objMember);
916         newParent = objMember;
917     }
918 
919     const QString documentText = m_semanticInfo.document->source();
920 
921     Rewriter rewriter(documentText, changeSet, QStringList());
922 
923     if (auto objDefinition = AST::cast<const AST::UiObjectDefinition*>(newParent)) {
924         AST::UiObjectMemberList *listInsertAfter = nullptr;
925         if (insertionOrderSpecified) {
926             if (insertAfter) {
927                 listInsertAfter = objDefinition->initializer->members;
928                 while (listInsertAfter && (listInsertAfter->member != insertAfter))
929                     listInsertAfter = listInsertAfter->next;
930             }
931         }
932 
933         if (auto moveScriptBinding = AST::cast<const AST::UiScriptBinding*>(toMove)) {
934             const QString propertyName = asString(moveScriptBinding->qualifiedId);
935             QString propertyValue;
936             {
937                 const int offset = moveScriptBinding->statement->firstSourceLocation().begin();
938                 const int length = moveScriptBinding->statement->lastSourceLocation().end() - offset;
939                 propertyValue = documentText.mid(offset, length);
940             }
941             Rewriter::BindingType bindingType = Rewriter::ScriptBinding;
942 
943             if (insertionOrderSpecified)
944                 *addedRange = rewriter.addBinding(objDefinition->initializer, propertyName, propertyValue, bindingType, listInsertAfter);
945             else
946                 *addedRange = rewriter.addBinding(objDefinition->initializer, propertyName, propertyValue, bindingType);
947         } else {
948             QString strToMove;
949             {
950                 const int offset = toMove->firstSourceLocation().begin();
951                 const int length = toMove->lastSourceLocation().end() - offset;
952                 strToMove = documentText.mid(offset, length);
953             }
954 
955             if (insertionOrderSpecified)
956                 *addedRange = rewriter.addObject(objDefinition->initializer, strToMove, listInsertAfter);
957             else
958                 *addedRange = rewriter.addObject(objDefinition->initializer, strToMove);
959         }
960     } else if (auto arrayBinding = AST::cast<AST::UiArrayBinding*>(newParent)) {
961         AST::UiArrayMemberList *listInsertAfter = nullptr;
962         if (insertionOrderSpecified) {
963             if (insertAfter) {
964                 listInsertAfter = arrayBinding->members;
965                 while (listInsertAfter && (listInsertAfter->member != insertAfter))
966                     listInsertAfter = listInsertAfter->next;
967             }
968         }
969         QString strToMove;
970         {
971             const int offset = toMove->firstSourceLocation().begin();
972             const int length = toMove->lastSourceLocation().end() - offset;
973             strToMove = documentText.mid(offset, length);
974         }
975 
976         if (insertionOrderSpecified)
977             *addedRange = rewriter.addObject(arrayBinding, strToMove, listInsertAfter);
978         else
979             *addedRange = rewriter.addObject(arrayBinding, strToMove);
980     } else if (AST::cast<AST::UiObjectBinding*>(newParent)) {
981         qDebug() << "TODO: Reparent to UiObjectBinding";
982         return;
983         // target is a property
984     } else {
985         return;
986     }
987 
988     rewriter.removeObjectMember(toMove, oldParent);
989 }
990 
parentItem()991 QStandardItem *QmlOutlineModel::parentItem()
992 {
993     QStandardItem *parent = m_currentItem->parent();
994     if (!parent)
995         parent = invisibleRootItem();
996     return parent;
997 }
998 
999 
asString(AST::UiQualifiedId * id)1000 QString QmlOutlineModel::asString(AST::UiQualifiedId *id)
1001 {
1002     QString text;
1003     for (; id; id = id->next) {
1004         if (!id->name.isEmpty())
1005             text += id->name.toString();
1006         else
1007             text += QLatin1Char('?');
1008 
1009         if (id->next)
1010             text += QLatin1Char('.');
1011     }
1012 
1013     return text;
1014 }
1015 
getLocation(AST::UiObjectMember * objMember)1016 SourceLocation QmlOutlineModel::getLocation(AST::UiObjectMember *objMember) {
1017     SourceLocation location;
1018     location = objMember->firstSourceLocation();
1019     location.length = objMember->lastSourceLocation().offset
1020             - objMember->firstSourceLocation().offset
1021             + objMember->lastSourceLocation().length;
1022     return location;
1023 }
1024 
getLocation(AST::ExpressionNode * exprNode)1025 SourceLocation QmlOutlineModel::getLocation(AST::ExpressionNode *exprNode) {
1026     SourceLocation location;
1027     location = exprNode->firstSourceLocation();
1028     location.length = exprNode->lastSourceLocation().offset
1029             - exprNode->firstSourceLocation().offset
1030             + exprNode->lastSourceLocation().length;
1031     return location;
1032 }
1033 
getLocation(AST::PatternPropertyList * propertyNode)1034 SourceLocation QmlOutlineModel::getLocation(AST::PatternPropertyList *propertyNode) {
1035     if (auto assignment = AST::cast<AST::PatternProperty *>(propertyNode->property))
1036         return getLocation(assignment);
1037     return propertyNode->firstSourceLocation(); // should never happen
1038 }
1039 
getLocation(AST::PatternProperty * propertyNode)1040 SourceLocation QmlOutlineModel::getLocation(AST::PatternProperty *propertyNode) {
1041     SourceLocation location;
1042     location = propertyNode->name->propertyNameToken;
1043     location.length = propertyNode->initializer->lastSourceLocation().end() - location.offset;
1044 
1045     return location;
1046 }
1047 
getIcon(AST::UiQualifiedId * qualifiedId)1048 QIcon QmlOutlineModel::getIcon(AST::UiQualifiedId *qualifiedId) {
1049     QIcon icon;
1050     if (qualifiedId) {
1051         QString name = asString(qualifiedId);
1052         if (name.contains(QLatin1Char('.')))
1053             name = name.split(QLatin1Char('.')).last();
1054 
1055         // TODO: get rid of namespace prefixes.
1056         icon = m_icons->icon(QLatin1String("Qt"), name);
1057         if (icon.isNull())
1058             icon = m_icons->icon(QLatin1String("QtWebkit"), name);
1059     }
1060     return icon;
1061 }
1062 
getAnnotation(AST::UiObjectInitializer * objectInitializer)1063 QString QmlOutlineModel::getAnnotation(AST::UiObjectInitializer *objectInitializer) {
1064     const QHash<QString,QString> bindings = getScriptBindings(objectInitializer);
1065 
1066     if (bindings.contains(QLatin1String("id")))
1067         return bindings.value(QLatin1String("id"));
1068 
1069     if (bindings.contains(QLatin1String("name")))
1070         return bindings.value(QLatin1String("name"));
1071 
1072     if (bindings.contains(QLatin1String("target")))
1073         return bindings.value(QLatin1String("target"));
1074 
1075     return QString();
1076 }
1077 
getAnnotation(AST::Statement * statement)1078 QString QmlOutlineModel::getAnnotation(AST::Statement *statement)
1079 {
1080     if (auto expr = AST::cast<const AST::ExpressionStatement*>(statement))
1081         return getAnnotation(expr->expression);
1082     return QString();
1083 }
1084 
getAnnotation(AST::ExpressionNode * expression)1085 QString QmlOutlineModel::getAnnotation(AST::ExpressionNode *expression)
1086 {
1087     if (!expression)
1088         return QString();
1089     QString source = m_semanticInfo.document->source();
1090     QString str = source.mid(expression->firstSourceLocation().begin(),
1091                              expression->lastSourceLocation().end()
1092                              - expression->firstSourceLocation().begin());
1093     // only show first line
1094     return str.left(str.indexOf(QLatin1Char('\n')));
1095 }
1096 
getScriptBindings(AST::UiObjectInitializer * objectInitializer)1097 QHash<QString,QString> QmlOutlineModel::getScriptBindings(AST::UiObjectInitializer *objectInitializer) {
1098     QHash <QString,QString> scriptBindings;
1099     for (AST::UiObjectMemberList *it = objectInitializer->members; it; it = it->next) {
1100         if (auto binding = AST::cast<const AST::UiScriptBinding*>(it->member)) {
1101             const QString bindingName = asString(binding->qualifiedId);
1102             scriptBindings.insert(bindingName, getAnnotation(binding->statement));
1103         }
1104     }
1105     return scriptBindings;
1106 }
1107 
1108 } // namespace Internal
1109 } // namespace QmlJSEditor
1110