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