1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include <filemanager/astobjecttextextractor.h>
35 #include <filemanager/objectlengthcalculator.h>
36 #include <filemanager/firstdefinitionfinder.h>
37 #include <customnotifications.h>
39 #include <qmljs/parser/qmljsengine_p.h>
41 #include "rewriterview.h"
42 #include "rewritingexception.h"
43 #include "textmodifier.h"
44 #include "texttomodelmerger.h"
45 #include "modelnodepositionstorage.h"
46 #include "modeltotextmerger.h"
47 #include "nodelistproperty.h"
48 #include "nodeproperty.h"
49 #include "invalidmodelnodeexception.h"
53 using namespace QmlDesigner::Internal;
55 namespace QmlDesigner {
57 RewriterView::Error::Error():
64 RewriterView::Error::Error(Exception *exception):
65 m_type(InternalError),
66 m_line(exception->line()),
68 m_description(exception->description()),
69 m_url(exception->file())
73 RewriterView::Error::Error(const QmlJS::DiagnosticMessage &qmlError, const QUrl &document):
75 m_line(qmlError.loc.startLine),
76 m_column(qmlError.loc.startColumn),
77 m_description(qmlError.message),
82 RewriterView::Error::Error(const QString &shortDescription) :
86 m_description(shortDescription),
92 QString RewriterView::Error::toString() const
96 if (m_type == ParseError)
97 str += tr("Error parsing");
98 else if (m_type == InternalError)
99 str += tr("Internal error");
101 if (url().isValid()) {
103 str += QLatin1Char(' ');
105 str += tr("\"%1\"").arg(url().toString());
110 str += QLatin1Char(' ');
111 str += tr("line %1").arg(line());
116 str += QLatin1Char(' ');
118 str += tr("column %1").arg(column());
123 str += description();
128 RewriterView::RewriterView(DifferenceHandling differenceHandling, QObject *parent):
129 AbstractView(parent),
130 m_differenceHandling(differenceHandling),
131 m_modificationGroupActive(false),
132 m_positionStorage(new ModelNodePositionStorage),
133 m_modelToTextMerger(new Internal::ModelToTextMerger(this)),
134 m_textToModelMerger(new Internal::TextToModelMerger(this)),
140 RewriterView::~RewriterView()
142 delete m_positionStorage;
145 Internal::ModelToTextMerger *RewriterView::modelToTextMerger() const
147 return m_modelToTextMerger.data();
150 Internal::TextToModelMerger *RewriterView::textToModelMerger() const
152 return m_textToModelMerger.data();
155 void RewriterView::modelAttached(Model *model)
157 AbstractView::modelAttached(model);
159 ModelAmender differenceHandler(m_textToModelMerger.data());
160 const QString qmlSource = m_textModifier->text();
161 if (m_textToModelMerger->load(qmlSource.toUtf8(), differenceHandler)) {
162 lastCorrectQmlSource = qmlSource;
166 void RewriterView::modelAboutToBeDetached(Model * /*model*/)
168 m_positionStorage->clear();
171 void RewriterView::nodeCreated(const ModelNode &createdNode)
173 Q_ASSERT(textModifier());
174 m_positionStorage->setNodeOffset(createdNode, ModelNodePositionStorage::INVALID_LOCATION);
175 if (textToModelMerger()->isActive())
178 modelToTextMerger()->nodeCreated(createdNode);
180 if (!isModificationGroupActive())
184 void RewriterView::nodeAboutToBeRemoved(const ModelNode &/*removedNode*/)
188 void RewriterView::nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange)
190 Q_ASSERT(textModifier());
191 if (textToModelMerger()->isActive())
194 modelToTextMerger()->nodeRemoved(removedNode, parentProperty, propertyChange);
196 if (!isModificationGroupActive())
200 void RewriterView::propertiesAdded(const ModelNode &/*node*/, const QList<AbstractProperty>& /*propertyList*/)
205 void RewriterView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList)
207 Q_ASSERT(textModifier());
208 if (textToModelMerger()->isActive())
212 foreach (const AbstractProperty &property, propertyList) {
213 if (property.isDefaultProperty() && property.isNodeListProperty()) {
214 m_removeDefaultPropertyTransaction = beginRewriterTransaction();
216 foreach (const ModelNode &node, property.toNodeListProperty().toModelNodeList()) {
217 modelToTextMerger()->nodeRemoved(node, property.toNodeAbstractProperty(), AbstractView::NoAdditionalChanges);
223 void RewriterView::propertiesRemoved(const QList<AbstractProperty>& propertyList)
225 Q_ASSERT(textModifier());
226 if (textToModelMerger()->isActive())
229 modelToTextMerger()->propertiesRemoved(propertyList);
231 if (m_removeDefaultPropertyTransaction.isValid())
232 m_removeDefaultPropertyTransaction.commit();
234 if (!isModificationGroupActive())
238 void RewriterView::variantPropertiesChanged(const QList<VariantProperty>& propertyList, PropertyChangeFlags propertyChange)
240 Q_ASSERT(textModifier());
241 if (textToModelMerger()->isActive())
244 QList<AbstractProperty> usefulPropertyList;
245 foreach (const VariantProperty &property, propertyList)
246 usefulPropertyList.append(property);
248 modelToTextMerger()->propertiesChanged(usefulPropertyList, propertyChange);
250 if (!isModificationGroupActive())
254 void RewriterView::bindingPropertiesChanged(const QList<BindingProperty>& propertyList, PropertyChangeFlags propertyChange)
256 Q_ASSERT(textModifier());
257 if (textToModelMerger()->isActive())
260 QList<AbstractProperty> usefulPropertyList;
261 foreach (const BindingProperty &property, propertyList)
262 usefulPropertyList.append(property);
264 modelToTextMerger()->propertiesChanged(usefulPropertyList, propertyChange);
266 if (!isModificationGroupActive())
270 void RewriterView::nodeReparented(const ModelNode &node, const NodeAbstractProperty &newPropertyParent, const NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange)
272 Q_ASSERT(textModifier());
273 if (textToModelMerger()->isActive())
276 modelToTextMerger()->nodeReparented(node, newPropertyParent, oldPropertyParent, propertyChange);
278 if (!isModificationGroupActive())
282 void RewriterView::nodeAboutToBeReparented(const ModelNode &/*node*/, const NodeAbstractProperty &/*newPropertyParent*/, const NodeAbstractProperty &/*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/)
287 void RewriterView::importAdded(const Import &import)
289 Q_ASSERT(textModifier());
290 if (textToModelMerger()->isActive())
293 if (import.url() == "Qt")
294 foreach (const Import &import, model()->imports()) {
295 if (import.url() == "QtQuick")
296 return; //QtQuick magic we do not have to add an import for Qt
299 modelToTextMerger()->addImport(import);
301 if (!isModificationGroupActive())
305 void RewriterView::importRemoved(const Import &import)
307 Q_ASSERT(textModifier());
308 if (textToModelMerger()->isActive())
311 modelToTextMerger()->removeImport(import);
313 if (!isModificationGroupActive())
317 void RewriterView::fileUrlChanged(const QUrl &/*oldUrl*/, const QUrl &/*newUrl*/)
321 void RewriterView::nodeIdChanged(const ModelNode& node, const QString& newId, const QString& oldId)
323 Q_ASSERT(textModifier());
324 if (textToModelMerger()->isActive())
327 modelToTextMerger()->nodeIdChanged(node, newId, oldId);
329 if (!isModificationGroupActive())
333 void RewriterView::nodeOrderChanged(const NodeListProperty &listProperty, const ModelNode &movedNode, int /*oldIndex*/)
335 Q_ASSERT(textModifier());
336 if (textToModelMerger()->isActive())
339 const QList<ModelNode> nodes = listProperty.toModelNodeList();
341 ModelNode trailingNode;
342 int newIndex = nodes.indexOf(movedNode);
343 if (newIndex + 1 < nodes.size())
344 trailingNode = nodes.at(newIndex + 1);
345 modelToTextMerger()->nodeSlidAround(movedNode, trailingNode);
347 if (!isModificationGroupActive())
351 void RewriterView::rootNodeTypeChanged(const QString &type, int majorVersion, int minorVersion)
353 Q_ASSERT(textModifier());
354 if (textToModelMerger()->isActive())
357 modelToTextMerger()->nodeTypeChanged(rootModelNode(), type, majorVersion, minorVersion);
359 if (!isModificationGroupActive())
363 void RewriterView::customNotification(const AbstractView * /*view*/, const QString &identifier, const QList<ModelNode> & /* nodeList */, const QList<QVariant> & /*data */)
365 if (identifier == StartRewriterAmend || identifier == EndRewriterAmend)
366 return; // we emitted this ourselves, so just ignore these notifications.
368 if (identifier == ("__start rewriter transaction__")) {
370 setModificationGroupActive(true);
372 else if (identifier == ("__end rewriter transaction__")) {
374 Q_ASSERT(transactionLevel >= 0);
377 if (transactionLevel == 0)
379 setModificationGroupActive(false);
380 applyModificationGroupChanges();
384 void RewriterView::scriptFunctionsChanged(const ModelNode &/*node*/, const QStringList &/*scriptFunctionList*/)
388 void RewriterView::instancePropertyChange(const QList<QPair<ModelNode, QString> > &/*propertyList*/)
392 void RewriterView::instancesCompleted(const QVector<ModelNode> &/*completedNodeList*/)
397 void RewriterView::selectedNodesChanged(const QList<ModelNode> & /* selectedNodeList, */, const QList<ModelNode> & /*lastSelectedNodeList */)
401 bool RewriterView::isModificationGroupActive() const
403 return m_modificationGroupActive;
406 void RewriterView::setModificationGroupActive(bool active)
408 m_modificationGroupActive = active;
411 TextModifier *RewriterView::textModifier() const
413 return m_textModifier;
416 void RewriterView::setTextModifier(TextModifier *textModifier)
419 disconnect(m_textModifier, SIGNAL(textChanged()), this, SLOT(qmlTextChanged()));
421 m_textModifier = textModifier;
424 connect(m_textModifier, SIGNAL(textChanged()), this, SLOT(qmlTextChanged()));
427 QString RewriterView::textModifierContent() const
430 return textModifier()->text();
435 void RewriterView::applyModificationGroupChanges()
437 Q_ASSERT(transactionLevel == 0);
441 void RewriterView::applyChanges()
443 if (modelToTextMerger()->hasNoPendingChanges())
444 return; // quick exit: nothing to be done.
448 if (inErrorState()) {
449 const QString content = textModifierContent();
450 qDebug() << "RewriterView::applyChanges() got called while in error state. Will do a quick-exit now.";
451 qDebug() << "Content:" << content;
452 throw RewritingException(__LINE__, __FUNCTION__, __FILE__, "RewriterView::applyChanges() already in error state", content);
456 modelToTextMerger()->applyChanges();
457 if (!errors().isEmpty()) {
458 enterErrorState(errors().first().description());
460 } catch (Exception &e) {
461 const QString content = textModifierContent();
462 qDebug() << "RewriterException:" << m_rewritingErrorMessage;
463 qDebug() << "Content:" << content;
464 enterErrorState(e.description());
467 if (inErrorState()) {
468 const QString content = textModifierContent();
469 qDebug() << "RewriterException:" << m_rewritingErrorMessage;
470 qDebug() << "Content:" << content;
471 if (!errors().isEmpty())
472 qDebug() << "Error:" << errors().first().description();
473 throw RewritingException(__LINE__, __FUNCTION__, __FILE__, m_rewritingErrorMessage, content);
477 QList<RewriterView::Error> RewriterView::errors() const
482 void RewriterView::clearErrors()
485 emit errorsChanged(m_errors);
488 void RewriterView::setErrors(const QList<RewriterView::Error> &errors)
491 emit errorsChanged(m_errors);
494 void RewriterView::addError(const RewriterView::Error &error)
496 m_errors.append(error);
497 emit errorsChanged(m_errors);
500 void RewriterView::enterErrorState(const QString &errorMessage)
502 m_rewritingErrorMessage = errorMessage;
505 void RewriterView::resetToLastCorrectQml()
507 m_textModifier->textDocument()->undo();
508 m_textModifier->textDocument()->clearUndoRedoStacks(QTextDocument::RedoStack);
509 ModelAmender differenceHandler(m_textToModelMerger.data());
510 m_textToModelMerger->load(m_textModifier->text().toUtf8(), differenceHandler);
515 QMap<ModelNode, QString> RewriterView::extractText(const QList<ModelNode> &nodes) const
517 QmlDesigner::ASTObjectTextExtractor extract(m_textModifier->text());
518 QMap<ModelNode, QString> result;
520 foreach (const ModelNode &node, nodes) {
521 const int nodeLocation = m_positionStorage->nodeOffset(node);
523 if (nodeLocation == ModelNodePositionStorage::INVALID_LOCATION)
524 result.insert(node, QString());
526 result.insert(node, extract(nodeLocation));
532 int RewriterView::nodeOffset(const ModelNode &node) const
534 return m_positionStorage->nodeOffset(node);
538 * \return the length of the node's text, or -1 if it wasn't found or if an error
541 int RewriterView::nodeLength(const ModelNode &node) const
543 ObjectLengthCalculator objectLengthCalculator;
545 if (objectLengthCalculator(m_textModifier->text(), nodeOffset(node), length))
551 int RewriterView::firstDefinitionInsideOffset(const ModelNode &node) const
553 FirstDefinitionFinder firstDefinitionFinder(m_textModifier->text());
554 return firstDefinitionFinder(nodeOffset(node));
557 int RewriterView::firstDefinitionInsideLength(const ModelNode &node) const
559 FirstDefinitionFinder firstDefinitionFinder(m_textModifier->text());
560 const int offset = firstDefinitionFinder(nodeOffset(node));
562 ObjectLengthCalculator objectLengthCalculator;
564 if (objectLengthCalculator(m_textModifier->text(), offset, length))
570 bool RewriterView::modificationGroupActive()
572 return m_modificationGroupActive;
575 bool RewriterView::renameId(const QString& oldId, const QString& newId)
578 return textModifier()->renameId(oldId, newId);
583 QmlJS::LookupContext *RewriterView::lookupContext() const
585 return textToModelMerger()->lookupContext();
588 QmlJS::Document *RewriterView::document() const
590 return textToModelMerger()->document();
593 void RewriterView::qmlTextChanged()
598 if (m_textToModelMerger && m_textModifier) {
599 const QString newQmlText = m_textModifier->text();
601 // qDebug() << "qmlTextChanged:" << newQmlText;
603 switch (m_differenceHandling) {
605 ModelValidator differenceHandler(m_textToModelMerger.data());
606 if (m_textToModelMerger->load(newQmlText.toUtf8(), differenceHandler)) {
607 lastCorrectQmlSource = newQmlText;
614 emitCustomNotification(StartRewriterAmend);
615 ModelAmender differenceHandler(m_textToModelMerger.data());
616 if (m_textToModelMerger->load(newQmlText, differenceHandler)) {
617 lastCorrectQmlSource = newQmlText;
619 emitCustomNotification(EndRewriterAmend);