Skip to content

fix: persist and restore mark color highlights across sessions - #524

Open
LiHua000 wants to merge 1 commit into
linuxdeepin:release/eaglefrom
LiHua000:fix-328067-markcolor
Open

fix: persist and restore mark color highlights across sessions#524
LiHua000 wants to merge 1 commit into
linuxdeepin:release/eaglefrom
LiHua000:fix-328067-markcolor

Conversation

@LiHua000

@LiHua000 LiHua000 commented Aug 17, 2026

Copy link
Copy Markdown

根因分析

PMS 328067「文本编辑器无法保存标记颜色」——标记颜色功能在「数据模型 / 保存路径 / 恢复路径」三层均未接入持久化:

  • 颜色仅存 TextEdit 内存容器 m_markOperationssrc/editor/dtextedit.h:641)/m_wordMarkSelectionsdtextedit.h:639),随对象销毁消失,无序列化出口;
  • 保存路径 TextFileSaver::saveToFilesrc/common/text_file_saver.cpp:132)经 document->toPlainText() 只落盘纯文本,丢弃所有 ExtraSelection 颜色;
  • 会话 JSON(src/startmanager.cpp:162-205)与启动恢复(startmanager.cpp:318-372)均无标记颜色键,重开仅恢复书签与焦点;
  • 同项目书签已完整持久化(startmanager.cpp:28/822/862/894)作为「可做而未做」的直接对照。

根因已通过独立复核(✅ 高置信)。

修复方案

仿书签持久化范式(方案 A)补全标记颜色的「序列化 / 保存触发 / 打开恢复」三层:

  1. 新增隐藏配置项 advance.editor.markcolor,按文件路径持久化标记颜色({localPath, marks:[{start,end,color,type,matchText,time}]})。
  2. StartManager 新增 initMarkColor/saveMarkColor/recordMarkColor/findMarkColorm_markColorTable:启动读取、关窗/会话保存、变更缓存、按路径查找——与书签逐环节对应。
  3. 保存触发:会话保存 autoBackupFile 与关闭标签页 closeTab 处记录当前标记颜色;slotCloseWindowautoBackupFile 处落盘。
  4. TextEdit 新增 getMarkColorInfo(复用既有 convertMarkToReplace)与 setMarkColorList(用当前文档光标重建选中区间后调既有 manualUpdateAllMark 恢复内存标记并 renderAllSelections)。
  5. 打开恢复:EditWrapper::handleFileLoadFinished 文件内容加载完成后按路径 findMarkColor + setMarkColorList 恢复——位置型数据需待内容就绪,故不能像行号型书签那样在 openFile 后同步调用。

复用既有 MarkReplaceInfo/convertMarkToReplace/manualUpdateAllMark 模型,未修改任何既有函数签名或函数体,纯新增。

改动安全评估

低风险。纯新增 192 行、0 删除,不修改任何函数签名/公开成员,不触碰既有函数体;被复用的 manualUpdateAllMark/convertMarkToReplace 仅作只读入口调用,既有撤销/重做调用方(changemarkcommand.cpp)零影响;setMarkColorListqBound 夹紧位置避免文件被外部修改后越界。详见 change-safety.md

测试建议

  1. 打开文本文件 → 选中文字 → 右键「标记」选色 → 保存 → 关闭 → 重开:颜色应恢复。
  2. 整行标记 / 全文标记 / 关键字标记(MarkAllMatch)各类型分别验证保存重开恢复。
  3. 清空所有标记后关闭重开:不应残留旧标记。
  4. 文件被外部编辑器修改后重开:标记不致越界崩溃(位置被夹紧,不适配者被丢弃)。

关联

Summary by Sourcery

Persist and restore per-file text mark color highlights across editor sessions using a new configuration-backed store and integration with startup, auto-backup, tab closing, and file load flows.

New Features:

  • Add support for saving mark color highlight metadata per file to settings and restoring it when files are reopened.

Enhancements:

  • Extend StartManager and TextEdit with mark-color bookkeeping APIs and data structures aligned with existing bookmark persistence.
  • Ensure mark color positions are clamped to current document length to remain robust against external file modifications.

1. 新增隐藏配置项 advance.editor.markcolor 用于按文件路径持久化标记颜色信息;
2. 在 StartManager 中新增 initMarkColor/saveMarkColor/recordMarkColor/findMarkColor,仿书签范式实现标记颜色的启动读取、关窗/会话保存、变更缓存与按路径查找;
3. 在会话保存(autoBackupFile)与关闭标签页(closeTab)处记录当前标记颜色,在关窗与周期备份处落盘;
4. 在 TextEdit 中新增 getMarkColorInfo/setMarkColorList,复用既有 MarkReplaceInfo/convertMarkToReplace/manualUpdateAllMark 模型,用当前文档光标重建选中区间后恢复内存标记并刷新显示;
5. 在 EditWrapper::handleFileLoadFinished 文件内容加载完成后按路径恢复标记颜色,解决位置型数据需待内容就绪的时序问题;

=====================================

1. added hidden config option advance.editor.markcolor to persist mark color info keyed by file path;
2. added initMarkColor/saveMarkColor/recordMarkColor/findMarkColor in StartManager, mirroring the bookmark pattern for startup load, close/session save, change cache and path-based lookup;
3. record current mark color on session save (autoBackupFile) and on tab close (closeTab), persist on window close and periodic backup;
4. added getMarkColorInfo/setMarkColorList in TextEdit, reusing the existing MarkReplaceInfo/convertMarkToReplace/manualUpdateAllMark model, rebuilding selections with the live document cursor before restoring in-memory marks and refreshing display;
5. restore mark color in EditWrapper::handleFileLoadFinished after content is loaded, fixing the timing requirement that position-based data needs the content ready;

Log: 修复文本编辑器标记颜色无法保存的问题,新增标记颜色的持久化与重开恢复机制,标记颜色在保存关闭后重开文件可正确恢复

Bug: https://pms.uniontech.com/bug-view-328067.html

@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.

Sorry @LiHua000, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: LiHua000

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 Aug 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Persist and restore text editor mark color highlights across sessions by adding StartManager-backed serialization of mark metadata, plumbing capture on save/close, and restoring after file load via TextEdit helpers.

Sequence diagram for persisting and restoring mark color highlights

sequenceDiagram
    actor User
    participant Window
    participant EditWrapper
    participant TextEdit
    participant StartManager
    participant Settings

    %% Persist marks when closing a tab
    User->>Window: closeTab(filePath)
    Window->>TextEdit: textEditor()->getMarkColorInfo()
    TextEdit-->>Window: QList<MarkReplaceInfo>
    Window->>TextEdit: textEditor()->getTruePath()
    TextEdit-->>Window: markLocalPath
    Window->>StartManager: recordMarkColor(markLocalPath, markColorInfo)

    %% Periodic/session save of marks
    StartManager->>StartManager: autoBackupFile()
    StartManager->>TextEdit: wrapper->textEditor()->getMarkColorInfo()
    TextEdit-->>StartManager: QList<MarkReplaceInfo>
    StartManager->>StartManager: recordMarkColor(localPath, markColorList)
    StartManager->>StartManager: saveMarkColor()
    StartManager->>Settings: option(s_markColorKey)->setValue(recordInfo)

    %% App start: load cached marks
    StartManager->>StartManager: initMarkColor()
    StartManager->>Settings: value(s_markColorKey)
    Settings-->>StartManager: QStringList
    StartManager->>StartManager: m_markColorTable.insert(filePath, markList)

    %% File reopen: restore marks after content load
    User->>EditWrapper: open file
    EditWrapper->>TextEdit: handleFileLoadFinished(...)
    EditWrapper->>TextEdit: m_pTextEdit->getTruePath()
    TextEdit-->>EditWrapper: markLocalPath
    EditWrapper->>StartManager: findMarkColor(markLocalPath)
    StartManager-->>EditWrapper: QList<MarkReplaceInfo>
    EditWrapper->>TextEdit: setMarkColorList(markColorInfo)
    TextEdit->>TextEdit: manualUpdateAllMark(markList)
Loading

File-Level Changes

Change Details Files
Introduce StartManager-backed persistence layer for mark color metadata keyed by file path and stored in settings.
  • Add s_markColorKey setting identifier and m_markColorTable hash to StartManager for tracking per-file mark color lists.
  • Implement recordMarkColor/findMarkColor APIs to update and query cached mark color info.
  • Implement initMarkColor to read JSON-encoded mark color records from settings, validate file existence, and populate m_markColorTable.
  • Implement saveMarkColor to serialize m_markColorTable back to settings as compact JSON, pruning missing files or empty mark lists.
  • Wire initMarkColor into StartManager construction and saveMarkColor into autoBackupFile/slotCloseWindow to ensure periodic and shutdown persistence.
src/startmanager.cpp
src/startmanager.h
src/resources/settings.json
Expose mark color capture and restoration utilities on TextEdit using existing MarkReplaceInfo and mark operation logic.
  • Add getMarkColorInfo to convert current in-memory mark operations into absolute-position MarkReplaceInfo list for persistence.
  • Add setMarkColorList to rebuild mark operations from persisted MarkReplaceInfo using bounded QTextCursor ranges and manualUpdateAllMark.
  • Ensure cursor positions are clamped to current document length to avoid out-of-range issues after external file edits.
src/editor/dtextedit.cpp
src/editor/dtextedit.h
Hook mark color persistence into editor lifecycle: capture on backup/close and restore after file load completes.
  • In StartManager::autoBackupFile, query each wrapper’s TextEdit for mark color info and record it alongside bookmarks before saving.
  • In Window::closeTab, record mark color info for the closing tab via StartManager, clearing records when no marks remain.
  • In EditWrapper::handleFileLoadFinished, after text load completion, fetch persisted mark color info from StartManager by true path and call setMarkColorList to reapply highlights.
src/startmanager.cpp
src/widgets/window.cpp
src/editor/editwrapper.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

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

★ 总体评分:60分

■ 【总体评价】

代码实现了文本标记颜色的持久化存储与恢复功能,但存在低危资源耗尽安全漏洞
因存在1个低危安全漏洞,触发安全一票否决上限规则,强制扣减40分

■ 【详细分析】

  • 1.语法逻辑(基本正确)✓

代码在边界处理上表现严谨,setMarkColorList中使用qBound有效防止了文件被外部修改后位置越界的问题;initMarkColor中包含了对QJsonParseError的完整校验;针对qint64类型的time字段,采用toDoublestatic_cast结合的方式规避了JSON中无64位整型导致的精度截断问题。
建议:在setMarkColorList中可增加对start > end的防御性交换逻辑,以彻底避免因异常数据导致光标反向选区。

  • 2.代码质量(良好)✓

新增代码高度遵循了项目既有架构(完全对标initBookmarksaveBookmark等函数的设计模式),命名规范清晰(如s_markColorKeym_markColorTable),头文件中的注释使用了Doxygen标准格式,业务逻辑解耦合理。
建议:saveMarkColorinitMarkColor中存在部分结构相似的JSON字段映射代码,未来若字段增多可考虑抽取为公共的序列化/反序列化方法。

  • 3.代码性能(良好)✓

持久化过程仅在文件加载完成和标签页关闭时触发,属于低频操作。saveMarkColor中遍历哈希表并调用QFileInfo::exists会带来一定的磁盘I/O开销,但在编辑器实际使用场景中,同时打开的文件数量受限,不会引发性能瓶颈。
建议:若未来需进一步优化,可在内存中维护一个布尔标记位记录文件是否存在,减少保存时的系统调用次数。

  • 4.代码安全(存在 1 个安全漏洞(低危1个))✕

漏洞对比统计:新增漏洞 1 个,减少漏洞 0 个,持平 0 个
总体风险描述:配置文件若被恶意篡改,由于缺乏数据量级校验,可被利用实施本地拒绝服务攻击,攻击面为settings.json文件。

  • 安全漏洞1(低危):资源耗尽 在 StartManager::initMarkColor 中,从 settings.json 读取并解析 marksArray 时,未对数组长度进行校验。如果配置文件被恶意篡改或损坏,注入超大规模的 JSON 数组,会导致在循环中无限制地分配 QListTextEdit::MarkReplaceInfo 内存,造成内存耗尽拒绝服务。——非常重要

  • 建议:在解析 marksArray 后增加最大长度限制校验,超过安全阈值则直接丢弃该文件的标记记录并跳过。

■ 【改进建议代码示例】

// 在 StartManager::initMarkColor 函数中增加数组大小校验
void StartManager::initMarkColor()
{
    // 定义单文件允许的最大标记数量,防止恶意配置导致内存耗尽
    const int maxMarksPerFile = 10000;

    // 遍历标记颜色信息列表
    QStringList markColorInfoList = Settings::instance()->settings->value(s_markColorKey).toStringList();
    for (const QString &markColorInfo : markColorInfoList) {
        QJsonParseError readError;
        QJsonDocument doc = QJsonDocument::fromJson(markColorInfo.toUtf8(), &readError);
        if (QJsonParseError::NoError == readError.error
                && !doc.isNull()) {
            QJsonObject obj = doc.object();
            QString filePath = obj.value("localPath").toString();

            // 判断文件是否仍存在,若不存在,则不保留标记颜色信息
            if (!filePath.isEmpty()
                    && QFileInfo::exists(filePath)) {
                QJsonArray marksArray = obj.value("marks").toArray();
                
                // 增加安全校验:限制单文件标记数量,防止内存耗尽
                if (marksArray.size() > maxMarksPerFile) {
                    continue;
                }

                QList<TextEdit::MarkReplaceInfo> markList;
                for (const QJsonValue &markValue : marksArray) {
                    QJsonObject markObj = markValue.toObject();
                    TextEdit::MarkReplaceInfo info;
                    info.opt.type = static_cast<TextEdit::MarkOperationType>(markObj.value("type").toInt());
                    info.opt.color = markObj.value("color").toString();
                    info.opt.matchText = markObj.value("matchText").toString();
                    info.start = markObj.value("start").toInt();
                    info.end = markObj.value("end").toInt();
                    info.time = static_cast<qint64>(markObj.value("time").toDouble());
                    markList.append(info);
                }

                if (!markList.isEmpty()) {
                    // 文件存在且标记颜色非空,缓存标记颜色信息
                    m_markColorTable.insert(filePath, markList);
                }
            }
        }
    }
}

@deepin-ci-robot

Copy link
Copy Markdown

@LiHua000: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
github-pr-review-ci e298fd7 link true /test github-pr-review-ci

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here.

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