From e3b6c684fb311e407302f3942774243aed471cf5 Mon Sep 17 00:00:00 2001 From: liujinchang Date: Wed, 28 Oct 2026 19:28:29 +0800 Subject: [PATCH] fix: column edit undo/redo cursor mutation causes wrong selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Root cause: redo() mutates m_ColumnEditSelections[i].cursor in place via insertText()/deletePreviousChar(), undo() then operates on wrong positions via deleteChar()/insertText() 2. Fix: save original cursor positions in constructors, redo() uses temp cursors to avoid mutation, undo() uses original positions with removeSelectedText(), sync restored selections to TextEdit 3. Impact: InsertTextUndoCommand, DeleteTextUndoCommand, DeleteTextUndoCommand2 column-edit paths fixed; single-cursor paths unchanged Log: fix column edit undo/redo cursor mutation causing wrong selection Influence: 1. Test column edit cut then undo restores all selections 2. Test column edit insert then undo removes inserted text correctly 3. Test column edit delete then undo restores deleted text 4. Verify single-cursor undo/redo unaffected fix: 修复列编辑模式撤销重做光标变异导致选中状态错误 1. 根因:redo() 原地变异 m_ColumnEditSelections[i].cursor 位置, undo() 的 deleteChar()/insertText() 在错误位置操作 2. 方案:构造时保存原始 cursor 位置,redo() 使用临时 cursor 不变异 selections,undo() 基于原始位置 removeSelectedText(),恢复后 同步 selections 到 TextEdit 3. 影响:InsertTextUndoCommand、DeleteTextUndoCommand、 DeleteTextUndoCommand2 列编辑路径已修复;单 cursor 路径不变 Log: 修复列编辑模式下剪切后撤销选中状态错误的问题 Influence: 1. 测试列编辑剪切后撤销是否恢复所有选中 2. 测试列编辑插入后撤销是否正确删除插入文本 3. 测试列编辑删除后撤销是否恢复删除文本 4. 验证单 cursor 撤销/重做不受影响 Bug: https://pms.uniontech.com/bug-view-241613.html PMS: BUG-241613 BUG-254289 --- src/editor/deletetextundocommand.cpp | 42 +++++++++++++++++++++------- src/editor/deletetextundocommand.h | 4 ++- src/editor/dtextedit.cpp | 6 ++++ src/editor/dtextedit.h | 1 + src/editor/inserttextundocommand.cpp | 24 ++++++++++++---- src/editor/inserttextundocommand.h | 3 +- 6 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/editor/deletetextundocommand.cpp b/src/editor/deletetextundocommand.cpp index 74516ff20..56d8fb067 100644 --- a/src/editor/deletetextundocommand.cpp +++ b/src/editor/deletetextundocommand.cpp @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later #include "deletetextundocommand.h" +#include "dtextedit.h" #include "editwrapper.h" #include #include @@ -36,6 +37,7 @@ DeleteTextUndoCommand::DeleteTextUndoCommand(QList &s { int cnt = m_ColumnEditSelections.size(); for (int i = 0; i < cnt; i++) { + m_originalPositions.append(m_ColumnEditSelections[i].cursor.position()); QTextCursor textCursor = m_ColumnEditSelections[i].cursor; if (textCursor.hasSelection()) { m_selectTextList.append(textCursor.selectedText()); @@ -67,11 +69,15 @@ void DeleteTextUndoCommand::undo() int cnt = m_ColumnEditSelections.size(); for (int i = 0; i < cnt; i++) { m_ColumnEditSelections[i].cursor.insertText(m_selectTextList[i]); - m_ColumnEditSelections[i].cursor.setPosition( - m_ColumnEditSelections[i].cursor.position() - m_selectTextList[i].length(), QTextCursor::KeepAnchor); + m_ColumnEditSelections[i].cursor.setPosition(m_originalPositions[i] - m_selectTextList[i].length(), QTextCursor::MoveAnchor); + m_ColumnEditSelections[i].cursor.setPosition(m_originalPositions[i], QTextCursor::KeepAnchor); } if (m_edit && !m_ColumnEditSelections.isEmpty()) { + TextEdit *pEdit = qobject_cast(m_edit); + if (pEdit) { + pEdit->syncColumnEditSelections(m_ColumnEditSelections); + } m_edit->setTextCursor(m_ColumnEditSelections.last().cursor); } } @@ -89,7 +95,10 @@ void DeleteTextUndoCommand::redo() } else { int cnt = m_ColumnEditSelections.size(); for (int i = 0; i < cnt; i++) { - m_ColumnEditSelections[i].cursor.deletePreviousChar(); + QTextCursor tempCursor(m_edit->document()); + tempCursor.setPosition(m_ColumnEditSelections[i].cursor.selectionStart()); + tempCursor.setPosition(m_ColumnEditSelections[i].cursor.selectionEnd(), QTextCursor::KeepAnchor); + tempCursor.deletePreviousChar(); } if (m_edit && !m_ColumnEditSelections.isEmpty()) { @@ -119,6 +128,7 @@ DeleteTextUndoCommand2::DeleteTextUndoCommand2(QList m_sInsertText.replace("\r\n", "\n"); int cnt = m_ColumnEditSelections.size(); for (int i = 0; i < cnt; i++) { + m_originalPositions.append(m_ColumnEditSelections[i].cursor.position()); QTextCursor textCursor = m_ColumnEditSelections[i].cursor; if (textCursor.hasSelection()) { m_selectTextList.append(textCursor.selectedText()); @@ -144,10 +154,17 @@ void DeleteTextUndoCommand2::undo() } else { int cnt = m_ColumnEditSelections.size(); for (int i = 0; i < cnt; i++) { - m_ColumnEditSelections[i].cursor.setPosition(m_beginPostion); m_ColumnEditSelections[i].cursor.insertText(m_selectTextList[i]); - m_ColumnEditSelections[i].cursor.setPosition(m_beginPostion); - m_edit->setTextCursor(m_ColumnEditSelections[i].cursor); + m_ColumnEditSelections[i].cursor.setPosition(m_originalPositions[i] - m_selectTextList[i].length(), QTextCursor::MoveAnchor); + m_ColumnEditSelections[i].cursor.setPosition(m_originalPositions[i], QTextCursor::KeepAnchor); + } + + if (m_edit && !m_ColumnEditSelections.isEmpty()) { + TextEdit *pEdit = qobject_cast(m_edit); + if (pEdit) { + pEdit->syncColumnEditSelections(m_ColumnEditSelections); + } + m_edit->setTextCursor(m_ColumnEditSelections.last().cursor); } } } @@ -185,9 +202,14 @@ void DeleteTextUndoCommand2::redo() } else { int cnt = m_ColumnEditSelections.size(); for (int i = 0; i < cnt; i++) { - m_beginPostion = m_ColumnEditSelections[i].cursor.selectionStart(); - m_ColumnEditSelections[i].cursor.deletePreviousChar(); - m_edit->setTextCursor(m_ColumnEditSelections[i].cursor); + QTextCursor tempCursor(m_edit->document()); + tempCursor.setPosition(m_ColumnEditSelections[i].cursor.selectionStart()); + tempCursor.setPosition(m_ColumnEditSelections[i].cursor.selectionEnd(), QTextCursor::KeepAnchor); + tempCursor.deletePreviousChar(); + } + + if (m_edit && !m_ColumnEditSelections.isEmpty()) { + m_edit->setTextCursor(m_ColumnEditSelections.last().cursor); } } } diff --git a/src/editor/deletetextundocommand.h b/src/editor/deletetextundocommand.h index efd69cb13..77c2173a9 100644 --- a/src/editor/deletetextundocommand.h +++ b/src/editor/deletetextundocommand.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -25,6 +25,7 @@ class DeleteTextUndoCommand : public QUndoCommand QString m_sInsertText; QList m_selectTextList; QList m_ColumnEditSelections; + QList m_originalPositions; int m_beginPos; // 记录光标删除前位置 }; @@ -42,6 +43,7 @@ class DeleteTextUndoCommand2 : public QUndoCommand QString m_sInsertText; QList m_selectTextList; QList m_ColumnEditSelections; + QList m_originalPositions; QPlainTextEdit* m_edit; int m_beginPostion {0}; bool m_iscurrLine {false}; diff --git a/src/editor/dtextedit.cpp b/src/editor/dtextedit.cpp index 1893f77cd..a0bab03e7 100644 --- a/src/editor/dtextedit.cpp +++ b/src/editor/dtextedit.cpp @@ -2231,6 +2231,12 @@ void TextEdit::renderAllSelections() setExtraSelections(finalSelections); } +void TextEdit::syncColumnEditSelections(const QList &selections) +{ + m_altModSelections = selections; + renderAllSelections(); +} + void TextEdit::updateMarkAllSelectColor() { isMarkAllLine(m_bIsMarkAllLine, m_strMarkAllLineColorName); diff --git a/src/editor/dtextedit.h b/src/editor/dtextedit.h index de2206a5c..80168d7b5 100644 --- a/src/editor/dtextedit.h +++ b/src/editor/dtextedit.h @@ -211,6 +211,7 @@ class TextEdit : public DPlainTextEdit Qt::CaseSensitivity caseFlag = Qt::CaseInsensitive); bool searchKeywordSeletion(QString keyword, QTextCursor cursor, bool findNext); void renderAllSelections(); + void syncColumnEditSelections(const QList &selections); bool clearMarkOperationForCursor(QTextCursor cursor); bool clearMarksForTextCursor(); diff --git a/src/editor/inserttextundocommand.cpp b/src/editor/inserttextundocommand.cpp index b02864ff4..2554d44af 100644 --- a/src/editor/inserttextundocommand.cpp +++ b/src/editor/inserttextundocommand.cpp @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later #include "inserttextundocommand.h" +#include "dtextedit.h" InsertTextUndoCommand::InsertTextUndoCommand(const QTextCursor &textcursor, const QString &text, QPlainTextEdit *edit, QUndoCommand *parent) : QUndoCommand(parent) @@ -23,6 +24,9 @@ InsertTextUndoCommand::InsertTextUndoCommand(QList &s , m_ColumnEditSelections(selections) { m_sInsertText.replace("\r\n", "\n"); + for (int i = 0; i < m_ColumnEditSelections.size(); i++) { + m_originalPositions.append(m_ColumnEditSelections[i].cursor.position()); + } } void InsertTextUndoCommand::undo() @@ -44,10 +48,18 @@ void InsertTextUndoCommand::undo() } else { int cnt = m_ColumnEditSelections.size(); for (int i = 0; i < cnt; i++) { - m_ColumnEditSelections[i].cursor.deleteChar(); + int pos = m_ColumnEditSelections[i].cursor.position(); + m_ColumnEditSelections[i].cursor.setPosition(pos - m_sInsertText.length(), QTextCursor::MoveAnchor); + m_ColumnEditSelections[i].cursor.setPosition(pos, QTextCursor::KeepAnchor); + m_ColumnEditSelections[i].cursor.removeSelectedText(); + m_ColumnEditSelections[i].cursor.setPosition(m_originalPositions[i]); } if (m_pEdit && !m_ColumnEditSelections.isEmpty()) { + TextEdit *pEdit = qobject_cast(m_pEdit); + if (pEdit) { + pEdit->syncColumnEditSelections(m_ColumnEditSelections); + } m_pEdit->setTextCursor(m_ColumnEditSelections.last().cursor); } } @@ -75,14 +87,14 @@ void InsertTextUndoCommand::redo() } else { int cnt = m_ColumnEditSelections.size(); for (int i = 0; i < cnt; i++) { - m_ColumnEditSelections[i].cursor.insertText(m_sInsertText); - m_ColumnEditSelections[i].cursor.setPosition(m_ColumnEditSelections[i].cursor.position() - m_sInsertText.length() + 1, - QTextCursor::KeepAnchor); + QTextCursor tempCursor(m_pEdit->document()); + tempCursor.setPosition(m_ColumnEditSelections[i].cursor.position()); + tempCursor.insertText(m_sInsertText); } if (m_pEdit && !m_ColumnEditSelections.isEmpty()) { QTextCursor curCursor = m_pEdit->textCursor(); - curCursor.setPosition(m_ColumnEditSelections.last().cursor.selectionEnd()); + curCursor.setPosition(m_ColumnEditSelections.last().cursor.position()); m_pEdit->setTextCursor(curCursor); } } diff --git a/src/editor/inserttextundocommand.h b/src/editor/inserttextundocommand.h index 393dc95c0..55d7f668d 100644 --- a/src/editor/inserttextundocommand.h +++ b/src/editor/inserttextundocommand.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -29,6 +29,7 @@ class InsertTextUndoCommand : public QUndoCommand int m_endPostion{0}; QString m_sInsertText; QList m_ColumnEditSelections; + QList m_originalPositions; QString m_selectText = QString(); };