fix(editor): use versioned dirty state and debounced cache writes, also avoid full string conversion of editor buffer #2145
Conversation
bajrangCoder
commented
Jun 2, 2026
- track editor dirty state with version metadata and CodeMirror Text equality
- debounce cache snapshots and flush pending writes on switch/pause/save
- avoid full doc string conversion for tab switches and equality checks
- use mtime metadata for external file change detection
- track editor dirty state with version metadata and CodeMirror Text equality - debounce cache snapshots and flush pending writes on switch/pause/save - avoid full doc string conversion for tab switches and equality checks - use mtime metadata for external file change detection
This comment has been minimized.
This comment has been minimized.
|
Preview Release for this, has been built. |
Greptile SummaryThis PR refactors the editor's dirty-state and crash-cache pipeline: it replaces the async text-comparison approach with version counters (
Confidence Score: 3/5Not safe to merge as-is: two correctness issues in the dirty-state code paths can silently prevent edit tracking or misreport conflict state. The formatOnSave path in saveFile.js can permanently silence markEdited() if the format executor throws, because the new dispatch listener returns early when markChanged === false and never sets up the checkTimeout that previously served as a fallback restore. The isUnsaved setter also silently advances savedVersion on every undo-to-clean transition, which can suppress a hasDiskConflict alert when the disk file is externally modified. src/lib/saveFile.js (formatOnSave markChanged guard) and src/lib/editorFile.js (isUnsaved setter side-effect on savedVersion) Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant CM as CodeMirror Dispatch
participant Listener as updateListener
participant File as EditorFile
participant Cache as CacheFS
User->>CM: keystroke
CM->>Listener: "update (docChanged=true)"
Listener->>File: "file.session = update.state"
alt "markChanged === false"
Listener-->>Listener: return early (no markEdited, no timeout)
else normal edit
Listener->>File: markEdited() docVersion++
Listener->>Listener: set checkTimeout
Listener-->>Listener: checkTimeout fires
Listener->>File: scheduleCacheWrite(1500ms)
File->>Cache: writeToCache()
end
Note over File,Cache: Tab switch or app pause
File->>File: flushCacheWrite()
File->>Cache: writeToCache() immediately
Note over File,Cache: Save
User->>File: save()
File->>File: snapshot savedDoc, savedVersion
File->>Cache: writeFile(data)
File->>Cache: stat() mtime
File->>File: markSaved()
Note over File,Cache: checkFiles
File->>Cache: stat() mtime
alt "mtime === savedMtime"
Cache-->>File: return early
else mtime differs and isUnsaved
File->>File: markDiskChanged conflict warning
else mtime differs and not isUnsaved
Cache->>File: readFile Text.eq comparison
alt content differs
File->>File: markLoaded scheduleCacheWrite(0) no-op
else content same
File->>File: markLoaded update savedMtime
end
end
Reviews (5): Last reviewed commit: "fix(editor): refresh cache after disk re..." | Re-trigger Greptile |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
|
@greptileai review again and update scores etc |
| if (appSettings.value.formatOnSave) { | ||
| editorManager.activeFile.markChanged = false; | ||
| acode.exec("format", false); | ||
| editorManager.activeFile.markChanged = true; | ||
| } |
There was a problem hiding this comment.
The
markChanged = true restoration is not inside a try/finally block. In the new dispatch-listener design, the checkTimeout callback (which used to serve as a backup restore for markChanged) no longer fires when markChanged === false, because the listener returns early before setting up the timeout. If acode.exec("format", false) throws or dispatches asynchronously, markChanged can be left permanently false for this file. Every subsequent user edit will be ignored by the listener — markEdited() never runs, docVersion never increments, and the unsaved indicator stays dark despite real edits.
| if (appSettings.value.formatOnSave) { | |
| editorManager.activeFile.markChanged = false; | |
| acode.exec("format", false); | |
| editorManager.activeFile.markChanged = true; | |
| } | |
| if (appSettings.value.formatOnSave) { | |
| editorManager.activeFile.markChanged = false; | |
| try { | |
| acode.exec("format", false); | |
| } finally { | |
| editorManager.activeFile.markChanged = true; | |
| } | |
| } |