Skip to content

fix: column edit undo/redo cursor mutation causes wrong selection - #700

Open
pppanghu77 wants to merge 1 commit into
linuxdeepin:release/eaglefrom
pppanghu77:agent/agent/3d6d2d32d08f
Open

pppanghu77 wants to merge 1 commit into
linuxdeepin:release/eaglefrom
pppanghu77:agent/agent/3d6d2d32d08f

Conversation

@pppanghu77

@pppanghu77 pppanghu77 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

修复:列编辑模式撤销/重做光标变异导致选中状态错误(PMS 241613 / 254289)

根因

redo() 在 m_ColumnEditSelections[i].cursor 上直接调用 insertText() / deletePreviousChar(),导致 cursor 位置被永久变异。undo() 随后在变异后的位置调用 deleteChar() / insertText(),删除了错误位置的字符。

修复方案(根因层修复,4 步)

  1. 构造时保存原始 cursor 位置:在 InsertTextUndoCommand、DeleteTextUndoCommand、DeleteTextUndoCommand2 列编辑构造函数中记录每个 selection 的原始 position 到 m_originalPositions
  2. redo() 不变异 m_ColumnEditSelections:使用临时 cursor 执行插入/删除,保持 m_ColumnEditSelections[i].cursor 不变
  3. undo() 基于原始位置删除:用 setPosition(originalPos, MoveAnchor) + setPosition(originalPos + text.length(), KeepAnchor) 选中范围后 removeSelectedText(),替代 deleteChar()
  4. syncColumnEditSelections() 保留但传入恢复后的 selections:undo 后 cursor 恢复到原始位置再传入 sync

影响范围

  • InsertTextUndoCommand 列编辑路径(redo/undo)
  • DeleteTextUndoCommand 列编辑路径(redo/undo)
  • DeleteTextUndoCommand2 列编辑路径(redo/undo)
  • TextEdit::syncColumnEditSelections() 同步逻辑
  • 单 cursor 路径不受影响

验证

PyQt5 最小 demo 旁路验证(repro/demo/demo.py):

  • buggy 路径(redo 变异 cursor + undo deleteChar):输出 'Xaa\nXbb\nXcc',BUG REPRODUCED: True
  • fixed 路径(redo 临时 cursor + undo removeSelectedText):输出 'aaa\nbbb\nccc',FIX VERIFIED: True
  • 多字符插入 // 测试:FIX VERIFIED (multi-char): True
  • redo-undo-redo 循环测试:REDO-UNDO-REDO CYCLE VERIFIED: True

关联

PMS: BUG-241613 BUG-254289

Summary by Sourcery

Fix column-edit undo/redo cursor handling so selections and restored text remain correct across repeated operations.

Bug Fixes:

  • Fix incorrect column-edit selections during undo/redo by preserving cursor positions and applying text changes without mutating stored selections.

Enhancements:

  • Synchronize restored column-edit selections with the editor after undo operations.

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: pppanghu77

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai

sourcery-ai Bot commented Oct 28, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fix column-edit undo/redo selection corruption by retaining original cursor positions, applying redo edits through temporary cursors, and synchronizing restored selections with the editor’s rendered state.

Sequence diagram for stable column-edit undo and redo

sequenceDiagram
    participant Command as UndoCommand
    participant Selection as ColumnEditSelections
    participant TempCursor as TemporaryCursor
    participant Editor as TextEdit

    Command->>Selection: Construct and store originalPositions
    Command->>TempCursor: redo() uses cursor position
    TempCursor->>TempCursor: insertText() or deletePreviousChar()
    Note over Selection: Selection cursors remain unchanged
    Command->>Selection: undo() restores text at originalPositions
    Selection->>Selection: removeSelectedText() or insertText()
    Command->>Editor: syncColumnEditSelections(selections)
    Editor->>Editor: renderAllSelections()
Loading

File-Level Changes

Change Details Files
Preserve immutable column-edit cursor anchors across undo/redo operations.
  • Record each selection’s original cursor position when constructing column-edit commands.
  • Use temporary cursors for redo insertion and deletion so stored selections are not mutated.
  • Restore undo cursors to their original positions after text changes.
src/editor/inserttextundocommand.cpp
src/editor/inserttextundocommand.h
src/editor/deletetextundocommand.cpp
src/editor/deletetextundocommand.h
Make column-edit selection rendering follow restored undo state.
  • Add a TextEdit synchronization helper that updates alternate-mode selections and rerenders them.
  • Invoke synchronization after column-edit undo restores cursor positions.
  • Keep the active text cursor aligned with the final column selection.
src/editor/dtextedit.cpp
src/editor/dtextedit.h
src/editor/inserttextundocommand.cpp
src/editor/deletetextundocommand.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

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
@pppanghu77
pppanghu77 force-pushed the agent/agent/3d6d2d32d08f branch from e27f298 to e3b6c68 Compare October 28, 2026 14:56
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 98 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 本次提交修复了列编辑模式下 undo/redo 操作导致光标位置错误的问题。通过保存原始光标位置、使用临时游标避免存储游标被修改、以及同步列编辑选区,正确解决了 cursor mutation 导致错误选区的问题。四维度均通过,无安全漏洞,代码质量存在轻微重复但整体良好。

🔍 详细分析

1. 语法逻辑 ✅

评价: 语法正确,逻辑清晰 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['逻辑正确,无需修改']


2. 代码质量 ✅

评价: 代码结构清晰,注释完整 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['建议将 qobject_cast + syncColumnEditSelections 模式提取为公共方法,减少重复代码', '建议在 m_originalPositions 相关代码处添加简要注释说明 cursor mutation 问题及修复原理']


3. 代码性能 ✅

评价: 性能良好,资源使用合理 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['性能良好,无需优化']


4. 代码安全 🔒

评价: 存在0个安全漏洞 ✅ 通过

🔐 发现 0 个安全漏洞

安全漏洞详情:
✅ 未发现安全漏洞

建议: ['安全合规,无需加固']


💡 改进建议代码示例

// 建议提取 sync 同步模式为公共辅助方法,减少重复代码
// 可在基类或工具函数中定义:
static void syncColumnEditSelectionsToEdit(QPlainTextEdit *edit, const QList<QTextEdit::ExtraSelection> &selections)
{
    if (!edit || selections.isEmpty()) {
        return;
    }
    TextEdit *pEdit = qobject_cast<TextEdit*>(edit);
    if (pEdit) {
        pEdit->syncColumnEditSelections(selections);
    }
    edit->setTextCursor(selections.last().cursor);
}

// 在各 undo 方法中调用:
// syncColumnEditSelectionsToEdit(m_edit, m_ColumnEditSelections);

本报告由 AI 代码审查工具自动生成

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants