QmlDesigner: Make it possible to reparent and slide in a transaction

NodeListProperty can slide and re-parent nodes in the same transaction

Fixes: QDS-7476
Change-Id: Ibc62e2bce5baca9465c320f0dfa1b9d408d04384
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Ali Kianian
2025-04-02 12:37:40 +03:00
parent 466558aa2c
commit cdf843160f
10 changed files with 153 additions and 39 deletions

View File

@@ -10,12 +10,14 @@ using namespace QmlDesigner::Internal;
AddObjectVisitor::AddObjectVisitor(QmlDesigner::TextModifier &modifier, AddObjectVisitor::AddObjectVisitor(QmlDesigner::TextModifier &modifier,
quint32 parentLocation, quint32 parentLocation,
quint32 nodeLocation,
const QString &content, const QString &content,
const PropertyNameList &propertyOrder): const PropertyNameList &propertyOrder)
QMLRewriter(modifier), : QMLRewriter(modifier)
m_parentLocation(parentLocation), , m_parentLocation(parentLocation)
m_content(content), , m_nodeLocation(nodeLocation)
m_propertyOrder(propertyOrder) , m_content(content)
, m_propertyOrder(propertyOrder)
{ {
} }
@@ -44,7 +46,9 @@ bool AddObjectVisitor::visit(QmlJS::AST::UiObjectDefinition *ast)
// FIXME: duplicate code in the QmlJS::Rewriter class, remove this // FIXME: duplicate code in the QmlJS::Rewriter class, remove this
void AddObjectVisitor::insertInto(QmlJS::AST::UiObjectInitializer *ast) void AddObjectVisitor::insertInto(QmlJS::AST::UiObjectInitializer *ast)
{ {
QmlJS::AST::UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder); QmlJS::AST::UiObjectMemberList *insertAfter = searchChildrenToInsertAfter(ast->members,
m_propertyOrder,
m_nodeLocation - 1);
int insertionPoint; int insertionPoint;
int depth; int depth;

View File

@@ -13,6 +13,7 @@ class AddObjectVisitor: public QMLRewriter
public: public:
AddObjectVisitor(QmlDesigner::TextModifier &modifier, AddObjectVisitor(QmlDesigner::TextModifier &modifier,
quint32 parentLocation, quint32 parentLocation,
quint32 nodeLocation,
const QString &content, const QString &content,
const PropertyNameList &propertyOrder); const PropertyNameList &propertyOrder);
@@ -25,6 +26,7 @@ private:
private: private:
quint32 m_parentLocation; quint32 m_parentLocation;
quint32 m_nodeLocation;
QString m_content; QString m_content;
PropertyNameList m_propertyOrder; PropertyNameList m_propertyOrder;
}; };

View File

@@ -75,12 +75,12 @@ bool QmlRefactoring::addToArrayMemberList(int parentLocation,
return visit(qmlDocument->qmlProgram()); return visit(qmlDocument->qmlProgram());
} }
bool QmlRefactoring::addToObjectMemberList(int parentLocation, const QString &content) bool QmlRefactoring::addToObjectMemberList(int parentLocation, int nodeLocation, const QString &content)
{ {
if (parentLocation < 0) if (parentLocation < 0)
return false; return false;
AddObjectVisitor visit(*textModifier, parentLocation, content, m_propertyOrder); AddObjectVisitor visit(*textModifier, parentLocation, nodeLocation, content, m_propertyOrder);
return visit(qmlDocument->qmlProgram()); return visit(qmlDocument->qmlProgram());
} }

View File

@@ -33,7 +33,7 @@ public:
bool removeImport(const Import &import); bool removeImport(const Import &import);
bool addToArrayMemberList(int parentLocation, PropertyNameView propertyName, const QString &content); bool addToArrayMemberList(int parentLocation, PropertyNameView propertyName, const QString &content);
bool addToObjectMemberList(int parentLocation, const QString &content); bool addToObjectMemberList(int parentLocation, int nodeLocation, const QString &content);
bool addProperty(int parentLocation, bool addProperty(int parentLocation,
PropertyNameView name, PropertyNameView name,
const QString &value, const QString &value,

View File

@@ -272,6 +272,47 @@ QmlJS::AST::UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(
return nullptr; return nullptr;
} }
QmlJS::AST::UiObjectMemberList *QMLRewriter::searchChildrenToInsertAfter(
QmlJS::AST::UiObjectMemberList *members, const PropertyNameList &propertyOrder, int pos)
{
if (pos < 0)
return searchMemberToInsertAfter(members, propertyOrder);
// An empty property name should be available in the propertyOrder List, which is the right place
// to define the objects there.
const int objectDefinitionInsertionPoint = propertyOrder.indexOf(PropertyName());
QmlJS::AST::UiObjectMemberList *lastObjectDef = nullptr;
QmlJS::AST::UiObjectMemberList *lastNonObjectDef = nullptr;
int objectPos = 0;
for (QmlJS::AST::UiObjectMemberList *iter = members; iter; iter = iter->next) {
QmlJS::AST::UiObjectMember *member = iter->member;
int idx = -1;
if (QmlJS::AST::cast<QmlJS::AST::UiObjectDefinition *>(member)) {
lastObjectDef = iter;
if (objectPos++ == pos)
break;
} else if (auto arrayBinding = QmlJS::AST::cast<QmlJS::AST::UiArrayBinding *>(member))
idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId).toUtf8());
else if (auto objectBinding = QmlJS::AST::cast<QmlJS::AST::UiObjectBinding *>(member))
idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId).toUtf8());
else if (auto scriptBinding = QmlJS::AST::cast<QmlJS::AST::UiScriptBinding *>(member))
idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId).toUtf8());
else if (QmlJS::AST::cast<QmlJS::AST::UiPublicMember *>(member))
idx = propertyOrder.indexOf("property");
if (idx < objectDefinitionInsertionPoint)
lastNonObjectDef = iter;
}
if (lastObjectDef)
return lastObjectDef;
else
return lastNonObjectDef;
}
void QMLRewriter::dump(const ASTPath &path) void QMLRewriter::dump(const ASTPath &path)
{ {
qCDebug(qmlRewriter) << "AST path with" << path.size() << "node(s):"; qCDebug(qmlRewriter) << "AST path with" << path.size() << "node(s):";

View File

@@ -56,6 +56,8 @@ protected:
QmlJS::AST::UiObjectMemberList *members, QmlJS::AST::UiObjectMemberList *members,
PropertyNameView propertyName, PropertyNameView propertyName,
const PropertyNameList &propertyOrder); const PropertyNameList &propertyOrder);
static QmlJS::AST::UiObjectMemberList *searchChildrenToInsertAfter(
QmlJS::AST::UiObjectMemberList *members, const PropertyNameList &propertyOrder, int pos = -1);
protected: protected:
bool didRewriting() const bool didRewriting() const

View File

@@ -63,34 +63,38 @@ QStringView toString(QmlRefactoring::PropertyType type)
bool AddPropertyRewriteAction::execute(QmlRefactoring &refactoring, ModelNodePositionStorage &positionStore) bool AddPropertyRewriteAction::execute(QmlRefactoring &refactoring, ModelNodePositionStorage &positionStore)
{ {
if (m_sheduledInHierarchy) { if (m_sheduledInHierarchy) {
const int nodeLocation = positionStore.nodeOffset(m_property.parentModelNode()); const int parentLocation = positionStore.nodeOffset(m_property.parentModelNode());
bool result = false; bool result = false;
if (m_propertyType != QmlRefactoring::ScriptBinding && m_property.isDefaultProperty()) { if (m_propertyType != QmlRefactoring::ScriptBinding && m_property.isDefaultProperty()) {
result = refactoring.addToObjectMemberList(nodeLocation, m_valueText); const int nodeLocation = movedAfterCreation()
? m_property.toNodeListProperty().indexOf(m_containedModelNode)
: -1;
result = refactoring.addToObjectMemberList(parentLocation, nodeLocation, m_valueText);
if (!result) { if (!result) {
qDebug() << "*** AddPropertyRewriteAction::execute failed in addToObjectMemberList(" qDebug() << "*** AddPropertyRewriteAction::execute failed in addToObjectMemberList("
<< nodeLocation << ',' << parentLocation << ',' << nodeLocation << ',' << m_valueText << ") **"
<< m_valueText << ") **"
<< info(); << info();
} }
} else if (m_property.isNodeListProperty() && m_property.toNodeListProperty().count() > 1) { } else if (m_property.isNodeListProperty() && m_property.toNodeListProperty().count() > 1) {
result = refactoring.addToArrayMemberList(nodeLocation, m_property.name(), m_valueText); result = refactoring.addToArrayMemberList(parentLocation, m_property.name(), m_valueText);
if (!result) { if (!result) {
qDebug() << "*** AddPropertyRewriteAction::execute failed in addToArrayMemberList(" qDebug() << "*** AddPropertyRewriteAction::execute failed in addToArrayMemberList("
<< nodeLocation << ',' << parentLocation << ',' << m_property.name() << ',' << m_valueText
<< m_property.name() << ',' << ") **" << info();
<< m_valueText << ") **"
<< info();
} }
} else { } else {
result = refactoring.addProperty(nodeLocation, m_property.name(), m_valueText, m_propertyType, m_property.dynamicTypeName()); result = refactoring.addProperty(parentLocation,
m_property.name(),
m_valueText,
m_propertyType,
m_property.dynamicTypeName());
if (!result) { if (!result) {
qDebug() << "*** AddPropertyRewriteAction::execute failed in addProperty(" qDebug() << "*** AddPropertyRewriteAction::execute failed in addProperty("
<< nodeLocation << ',' << m_property.name() << ',' << m_valueText << "," << parentLocation << ',' << m_property.name() << ',' << m_valueText << ","
<< toString(m_propertyType) << ") **" << info(); << toString(m_propertyType) << ") **" << info();
} }
} }
@@ -163,38 +167,44 @@ QString ChangeIdRewriteAction::info() const
bool ChangePropertyRewriteAction::execute(QmlRefactoring &refactoring, ModelNodePositionStorage &positionStore) bool ChangePropertyRewriteAction::execute(QmlRefactoring &refactoring, ModelNodePositionStorage &positionStore)
{ {
if (m_sheduledInHierarchy) { if (m_sheduledInHierarchy) {
const int nodeLocation = positionStore.nodeOffset(m_property.parentModelNode()); const int parentLocation = positionStore.nodeOffset(m_property.parentModelNode());
if (nodeLocation < 0) { if (parentLocation < 0) {
qWarning() << "*** ChangePropertyRewriteAction::execute ignored. Invalid node location"; qWarning() << "*** ChangePropertyRewriteAction::execute ignored. Invalid node location";
return true; return true;
} }
bool result = false; bool result = false;
if (m_propertyType != QmlRefactoring::ScriptBinding && m_property.isDefaultProperty()) { if (m_propertyType != QmlRefactoring::ScriptBinding && m_property.isDefaultProperty()) {
result = refactoring.addToObjectMemberList(nodeLocation, m_valueText); const int nodeLocation = movedAfterCreation()
? m_property.toNodeListProperty().indexOf(m_containedModelNode)
: -1;
result = refactoring.addToObjectMemberList(parentLocation, nodeLocation, m_valueText);
if (!result) { if (!result) {
qDebug() << "*** ChangePropertyRewriteAction::execute failed in addToObjectMemberList(" qDebug()
<< nodeLocation << ',' << "*** ChangePropertyRewriteAction::execute failed in addToObjectMemberList("
<< m_valueText << ") **" << parentLocation << ',' << nodeLocation << ',' << m_valueText << ") **"
<< info(); << info();
} }
} else if (m_propertyType == QmlRefactoring::ArrayBinding) { } else if (m_propertyType == QmlRefactoring::ArrayBinding) {
result = refactoring.addToArrayMemberList(nodeLocation, m_property.name(), m_valueText); result = refactoring.addToArrayMemberList(parentLocation, m_property.name(), m_valueText);
if (!result) { if (!result) {
qDebug() << "*** ChangePropertyRewriteAction::execute failed in addToArrayMemberList(" qDebug()
<< nodeLocation << ',' << "*** ChangePropertyRewriteAction::execute failed in addToArrayMemberList("
<< m_property.name() << ',' << parentLocation << ',' << m_property.name() << ',' << m_valueText << ") **"
<< m_valueText << ") **"
<< info(); << info();
} }
} else { } else {
result = refactoring.changeProperty(nodeLocation, m_property.name(), m_valueText, m_propertyType); result = refactoring.changeProperty(parentLocation,
m_property.name(),
m_valueText,
m_propertyType);
if (!result) { if (!result) {
qDebug() << "*** ChangePropertyRewriteAction::execute failed in changeProperty(" qDebug() << "*** ChangePropertyRewriteAction::execute failed in changeProperty("
<< nodeLocation << ',' << m_property.name() << ',' << m_valueText << ',' << parentLocation << ',' << m_property.name() << ',' << m_valueText << ','
<< toString(m_propertyType) << ") **" << info(); << toString(m_propertyType) << ") **" << info();
} }
} }
@@ -327,12 +337,13 @@ bool MoveNodeRewriteAction::execute(QmlRefactoring &refactoring,
bool inDefaultProperty = (m_movingNode.parentProperty().parentModelNode().metaInfo().defaultPropertyName() == m_movingNode.parentProperty().name()); bool inDefaultProperty = (m_movingNode.parentProperty().parentModelNode().metaInfo().defaultPropertyName() == m_movingNode.parentProperty().name());
result = refactoring.moveObjectBeforeObject(movingNodeLocation, newTrailingNodeLocation, inDefaultProperty); result = refactoring.moveObjectBeforeObject(movingNodeLocation,
newTrailingNodeLocation,
inDefaultProperty);
if (!result) { if (!result) {
qDebug() << "*** MoveNodeRewriteAction::execute failed in moveObjectBeforeObject(" qDebug() << "*** MoveNodeRewriteAction::execute failed in moveObjectBeforeObject("
<< movingNodeLocation << ',' << movingNodeLocation << ',' << newTrailingNodeLocation << ',' << inDefaultProperty
<< newTrailingNodeLocation << ") **" << ") **" << info();
<< info();
} }
return result; return result;

View File

@@ -73,12 +73,16 @@ public:
ModelNode containedModelNode() const ModelNode containedModelNode() const
{ return m_containedModelNode; } { return m_containedModelNode; }
bool movedAfterCreation() const { return m_movedAfterCreation; }
void setMovedAfterCreation(bool moved) { m_movedAfterCreation = moved; }
private: private:
AbstractProperty m_property; AbstractProperty m_property;
QString m_valueText; QString m_valueText;
QmlDesigner::QmlRefactoring::PropertyType m_propertyType; QmlDesigner::QmlRefactoring::PropertyType m_propertyType;
ModelNode m_containedModelNode; ModelNode m_containedModelNode;
bool m_sheduledInHierarchy; bool m_sheduledInHierarchy;
bool m_movedAfterCreation = false;
}; };
class ChangeIdRewriteAction: public RewriteAction class ChangeIdRewriteAction: public RewriteAction
@@ -127,12 +131,16 @@ public:
ModelNode containedModelNode() const ModelNode containedModelNode() const
{ return m_containedModelNode; } { return m_containedModelNode; }
bool movedAfterCreation() const { return m_movedAfterCreation; }
void setMovedAfterCreation(bool moved) { m_movedAfterCreation = moved; }
private: private:
AbstractProperty m_property; AbstractProperty m_property;
QString m_valueText; QString m_valueText;
QmlDesigner::QmlRefactoring::PropertyType m_propertyType; QmlDesigner::QmlRefactoring::PropertyType m_propertyType;
ModelNode m_containedModelNode; ModelNode m_containedModelNode;
bool m_sheduledInHierarchy; bool m_sheduledInHierarchy;
bool m_movedAfterCreation;
}; };
class ChangeTypeRewriteAction:public RewriteAction class ChangeTypeRewriteAction:public RewriteAction
@@ -238,6 +246,8 @@ public:
MoveNodeRewriteAction *asMoveNodeRewriteAction() override { return this; } MoveNodeRewriteAction *asMoveNodeRewriteAction() override { return this; }
ModelNode movingNode() const { return m_movingNode; }
private: private:
ModelNode m_movingNode; ModelNode m_movingNode;
ModelNode m_newTrailingNode; ModelNode m_newTrailingNode;

View File

@@ -39,6 +39,7 @@ void RewriteActionCompressor::operator()(QList<RewriteAction *> &actions,
compressAddEditRemoveNodeActions(actions); compressAddEditRemoveNodeActions(actions);
compressAddEditActions(actions, tabSettings); compressAddEditActions(actions, tabSettings);
compressAddReparentActions(actions); compressAddReparentActions(actions);
compressSlidesIntoNewNode(actions);
} }
void RewriteActionCompressor::compressImports(QList<RewriteAction *> &actions) const void RewriteActionCompressor::compressImports(QList<RewriteAction *> &actions) const
@@ -378,3 +379,45 @@ void RewriteActionCompressor::compressAddReparentActions(QList<RewriteAction *>
delete action; delete action;
} }
} }
void RewriteActionCompressor::compressSlidesIntoNewNode(QList<RewriteAction *> &actions) const
{
QList<RewriteAction *> actionsToRemove;
QMap<ModelNode, RewriteAction *> addedNodes;
for (RewriteAction *action : std::as_const(actions)) {
if (action->asAddPropertyRewriteAction() || action->asChangePropertyRewriteAction()) {
ModelNode containedNode;
if (AddPropertyRewriteAction *addAction = action->asAddPropertyRewriteAction())
containedNode = addAction->containedModelNode();
else if (ChangePropertyRewriteAction *changeAction = action->asChangePropertyRewriteAction())
containedNode = changeAction->containedModelNode();
if (!containedNode.isValid())
continue;
if (m_positionStore->nodeOffset(containedNode) != ModelNodePositionStorage::INVALID_LOCATION)
continue;
addedNodes.insert(containedNode, action);
} else if (MoveNodeRewriteAction *moveNodeAction = action->asMoveNodeRewriteAction()) {
RewriteAction *previousAction = addedNodes[moveNodeAction->movingNode()];
if (!previousAction)
continue;
if (AddPropertyRewriteAction *addAction = previousAction->asAddPropertyRewriteAction()) {
actionsToRemove.append(moveNodeAction);
addAction->setMovedAfterCreation(true);
} else if (ChangePropertyRewriteAction *changeAction = previousAction
->asChangePropertyRewriteAction()) {
actionsToRemove.append(moveNodeAction);
changeAction->setMovedAfterCreation(true);
}
}
}
for (RewriteAction *action : std::as_const(actionsToRemove)) {
actions.removeOne(action);
delete action;
}
}

View File

@@ -28,6 +28,7 @@ private:
void compressPropertyActions(QList<RewriteAction *> &actions) const; void compressPropertyActions(QList<RewriteAction *> &actions) const;
void compressAddEditActions(QList<RewriteAction *> &actions, const TextEditor::TabSettings &tabSettings) const; void compressAddEditActions(QList<RewriteAction *> &actions, const TextEditor::TabSettings &tabSettings) const;
void compressAddReparentActions(QList<RewriteAction *> &actions) const; void compressAddReparentActions(QList<RewriteAction *> &actions) const;
void compressSlidesIntoNewNode(QList<RewriteAction *> &actions) const;
private: private:
PropertyNameList m_propertyOrder; PropertyNameList m_propertyOrder;