From c2fb654681970bf13a948db2bb700ae5ccb9471f Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 11 Nov 2021 13:23:23 +0200 Subject: [PATCH] QmlDesigner: Handle undo/redo/manual edits while editing subcomponent Undo/redo stack is reset when changing between subcomponent and document edit modes, so we can assume any undo/redo is contained within the component. Manual edits can still happen outside the component code, but we can assume they are always limited to either outside or inside the component code, as the only way to make a manual edit that spans both is to delete a block that includes both, which invalidates the subcomponent anyway. Fixes: QDS-5392 Change-Id: I820de73f519215a9cb3672abc55d5aa60cce078a Reviewed-by: Mahmoud Badri Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- .../components/integration/designdocument.cpp | 5 ++ .../model/componenttextmodifier.cpp | 81 ++++++++++++------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index a5d904c495a..8b8b3b91253 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -279,6 +279,7 @@ bool DesignDocument::isDocumentLoaded() const void DesignDocument::resetToDocumentModel() { + plainTextEdit()->document()->clearUndoRedoStacks(); m_inFileComponentModel.reset(); } @@ -310,6 +311,8 @@ void DesignDocument::changeToDocumentModel() viewManager().detachRewriterView(); viewManager().detachViewsExceptRewriterAndComponetView(); + plainTextEdit()->document()->clearUndoRedoStacks(); + m_inFileComponentModel.reset(); m_inFileComponentTextModifier.reset(); @@ -345,6 +348,8 @@ void DesignDocument::changeToInFileComponentModel(ComponentTextModifier *textMod viewManager().detachRewriterView(); viewManager().detachViewsExceptRewriterAndComponetView(); + plainTextEdit()->document()->clearUndoRedoStacks(); + m_inFileComponentModel.reset(createInFileComponentModel()); m_inFileComponentModel->setTextModifier(m_inFileComponentTextModifier.data()); diff --git a/src/plugins/qmldesigner/designercore/model/componenttextmodifier.cpp b/src/plugins/qmldesigner/designercore/model/componenttextmodifier.cpp index 6aca50c4334..aaccaee40db 100644 --- a/src/plugins/qmldesigner/designercore/model/componenttextmodifier.cpp +++ b/src/plugins/qmldesigner/designercore/model/componenttextmodifier.cpp @@ -160,40 +160,65 @@ void ComponentTextModifier::handleOriginalTextChanged() const QString currentText = m_originalModifier->text(); - // Adjust for removal/addition of whitespace in the document - // Check that non-whitespace portion of the text is the same and count the whitespace diff const int oldLen = m_originalText.size(); const int newLen = currentText.size(); - int newSpace = 0; - int oldSpace = 0; - int newIdx = 0; - for (int oldIdx = 0; oldIdx < oldLen; ++oldIdx) { - const QChar oldChar = m_originalText[oldIdx]; - if (oldIdx == m_componentStartOffset) - m_componentStartOffset += newSpace - oldSpace; - if (oldIdx == m_componentEndOffset) { - m_componentEndOffset += newSpace - oldSpace; - break; + + if (oldLen != newLen) { + int newSpace = 0; + int oldSpace = 0; + int newIdx = 0; + int nonWhiteSpaceChangeIdx = -1; + int newStartOffset = m_componentStartOffset; + + // Adjust for removal/addition of whitespace in the document. + // Whitespace changes that happen when document is saved can be spread around throughout + // the entire document in multiple places. + // Check that non-whitespace portion of the text is the same and count the whitespace diff + for (int oldIdx = 0; oldIdx < oldLen; ++oldIdx) { + const QChar oldChar = m_originalText[oldIdx]; + if (oldIdx == m_componentStartOffset) + newStartOffset += newSpace - oldSpace; + if (oldIdx == m_componentEndOffset) { + m_componentEndOffset += newSpace - oldSpace; + m_componentStartOffset = newStartOffset; + m_originalText = currentText; + break; + } + + while (newIdx < newLen && currentText[newIdx].isSpace()) { + ++newSpace; + ++newIdx; + } + + if (oldChar.isSpace()) { + ++oldSpace; + continue; + } + + if (currentText[newIdx] != oldChar) { + nonWhiteSpaceChangeIdx = oldIdx; + // A non-whitespace change is a result of manual text edit or undo/redo operation. + // Assumption is that separate whitespace changes and a non-whitespace change can't + // both happen simultaneously, so break out of whitespace check loop. + break; + } else { + ++newIdx; + } } - while (newIdx < newLen && currentText[newIdx].isSpace()) { - ++newSpace; - ++newIdx; + if (nonWhiteSpaceChangeIdx >= 0) { + // For non-whitespace change, we assume the whole change is either before the component + // or inside the component. If the change spans both, it's likely the change is + // invalid anyway, and we don't care about trying to keep offsets up to date. + int diff = newLen - oldLen; + if (nonWhiteSpaceChangeIdx < m_componentEndOffset) + m_componentEndOffset += diff; + if (nonWhiteSpaceChangeIdx < m_componentStartOffset) + m_componentStartOffset += diff; + m_originalText = currentText; } - if (oldChar.isSpace()) { - ++oldSpace; - continue; - } - - if (currentText[newIdx] != oldChar) { - // Non-whitespace difference, we can't determine a valid offset in this case - // TODO: Needs proper handling to deal with undo/redo/arbitrary edits somehow (QDS-5392) - break; - } else { - ++newIdx; - } } - m_originalText = currentText; + emit textChanged(); }