[Bug]外部程序修改已打开文件后,编辑器仍显示「已修改」标记(用户未手动编辑)
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 65/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- typescript
- Domain
- frontend
Research direction
Start in src/web-ui/src/tools/editor/services/MonacoModelManager.ts, especially setupContentChangeListener() and updateModelContent(), then trace the external-sync path through CodeEditor.tsx and applyDiskSnapshotToEditor(). Compare the manager-level dirty state, component-level hasChanges state, and emitted events. Done means an external file refresh leaves the buffer and disk consistent without stale dirty indicators or an unnecessary unsaved prompt.
Written by the indexing model from the issue text.
Description
问题描述
当外部程序(AI 代理 / 脚本 / git checkout)修改了当前已在编辑器中打开的文件时,编辑器会正确地把可见缓冲区刷新为新磁盘内容,但标签页/编辑器仍显示「已修改 / 未保存更改」标记——即便用户从未手动输入任何内容,且此时缓冲区内容已与磁盘一致。
复现步骤
- 在编辑器中打开一个文件,保持标签页打开。
- 用外部程序修改磁盘上的同一文件(例如 AI 代理写文件,或
git checkout)。 - 编辑器检测到变更,并把可见内容刷新为最新磁盘内容。
- 观察:标签页/编辑器仍显示「已修改」标记。
期望行为
外部变更被同步、缓冲区与磁盘一致后,「已修改 / 脏」标记应被清除。
实际行为
脏标记残留(或短暂亮起),导致用户即使没有任何未保存编辑,也不得不「关闭而不保存」。
环境
- 操作系统:Windows
- OpenBitFun 版本:最新版(反馈时刚完成升级)
- 触发条件:外部程序写入已打开的文件
根因分析(代码级)
1. 全局脏标记监听器没有「程序化同步」抑制
src/web-ui/src/tools/editor/services/MonacoModelManager.ts — setupContentChangeListener():
const listener = model.onDidChangeContent(() => {
const metadata = this.modelMetadata.get(uriString);
if (metadata) {
const currentVersionId = model.getAlternativeVersionId();
metadata.isDirty = this.documentModels.has(uriString)
? model.getValue() !== metadata.originalContent
: currentVersionId !== metadata.savedVersionId;
// 同时广播 monaco-model-dirty-changed 和 ModelContentChanged
}
});
这里没有任何「正在同步」的防护。每一次 model.setValue()——包括外部磁盘同步——都会把 isDirty 重新计算为 true。
相比之下,src/web-ui/src/tools/editor/components/CodeEditor.tsx(约第 913 行)的组件级监听器是有防护的:
model.onDidChangeContent(() => {
if (isLoadingContentRef.current) {
return; // 加载期间被抑制
}
// ...
setHasChanges(changed);
});
因此同一次外部同步下,两套脏状态(hasChanges 状态 vs metadata.isDirty)会得出不一致的结果。
2. 脏标记同步置位、异步清除
CodeEditor.tsx — applyDiskSnapshotToEditor() 调用 applyExternalContentToModel(),后者执行 model.setValue(fileContent)(经全局监听器同步置脏),随后才在 queueMicrotask 里通过 monacoModelManager.markAsSaved(modelKey) 清除。
3. updateModelContent 默认 markAsSaved = false
MonacoModelManager.updateModelContent(filePath, content, markAsSaved = false):
model.setValue(content); // 全局监听器把 isDirty 置为 true
if (metadata) {
if (markAsSaved) { // 默认 false → 永远不会清除
metadata.savedVersionId = /* ... */;
metadata.originalContent = content;
metadata.isDirty = false;
}
// ...
}
任何把磁盘内容同步进已打开模型、但没有传 markAsSaved=true 的调用方,都会让模型永久处于脏状态。
4. 事件不对称
setValue 会广播 ModelContentChanged(标签页 / 文件列表的消费方据此把条目标脏),但 markAsSaved() 只派发 monaco-model-dirty-changed,不会发出对应的「内容已同步」事件。因此消费方会保留过期的脏标记。
修复建议
- 在 manager 层增加「正在同步」标志,让程序化
setValue不标脏(或当内容是磁盘真值时,让updateModelContent默认采用「已保存」语义)。 - 让脏状态单一来源化——UI 读取
metadata.isDirty,而不是维护一套平行的hasChanges。 - 外部同步时同步地清除脏标记,并广播一个对应的「内容已同步 / 已保存」事件给标签页 / 文件列表消费方。
- Dominant language
- Rust
- Stars
- 2.3k
- Forks
- 236
- Avg merge
- 3h 7m
- Merged PRs (30d)
- 596
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from GCWing/OpenBitFun
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
GCWing/OpenBitFun#2363 ·
-
question
Difficulty 1/5 Under an hour Newbie friendliness 78/100
GCWing/OpenBitFun#2340 ·
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
GCWing/OpenBitFun#3179 · 1 reaction ·
-
Difficulty 4/5 3-5 days Newbie friendliness 64/100
GCWing/OpenBitFun#3178 ·
-
[Bug]: 小应用无卸载功能 Open
Difficulty 3/5 1-2 days Newbie friendliness 55/100
GCWing/OpenBitFun#3158 ·
All issues in GCWing/OpenBitFun
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug team:backend track:services-maintenance
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
cowprotocol/services#4950 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
gitbutlerapp/gitbutler#15998 · 1 comment ·
-
bug triage:deciding
Difficulty 1/5 Under an hour Newbie friendliness 88/100
open-telemetry/otel-arrow#4132 ·