Reload .autocp when modified externally#170
Conversation
Previously .autocp was loaded once via `by lazy` and only written to, so any external edit (e.g. by an AI tool) would be silently overwritten by AutoCpStorageSaver on the next saveAllDocuments using stale in-memory state. Register a BulkFileListener that calls AutoCpStorage.reloadFromDisk() when the file's content changes on disk, re-parsing through the existing Migrations path and resetting the two MutableStateFlows. The saver also short-circuits when document text already matches the encoded db, preventing a write-fire-reload loop and avoiding spurious modify events. The IDE-internal warning panel (AutoCpFileEditor) is unchanged -- direct edits inside the IDE editor remain blocked.
When an external tool (e.g. an AI script generating problem sets) writes a fresh .autocp into a project directory that previously had none, the user's data disappears within seconds: the file shows up with only a stub SolutionFile entry, all problems gone. Mechanism: AutoCpStorage's databaseDelegate is `by lazy`, so on first access (tool window opening, a cpp file getting focus) it sees no .autocp on disk and initializes to AutoCpDB() default. The external write then arrives as VFileCreateEvent, but AutoCpExternalReloader filtered only VFileContentChangeEvent, so reloadFromDisk() never ran and in-memory stayed at the empty default. Any subsequent beforeAllDocumentsSaving (triggered by focus changes / doc saves) serialized that empty default and overwrote the disk content. If the user clicked an unassociated .cpp in between, ToolFactory's AssociateFilePanel injected a stub SolutionFile, which is what ended up on disk in place of the original data. Widen the listener filter to also accept VFileCreateEvent so the created file is parsed into memory before any save fires. Add a staleness guard in AutoCpStorageSaver: if in-memory equals DEFAULT_AUTO_CP_DB but the disk document is non-blank, reload from disk instead of overwriting it -- covers the theoretical race where beforeAllDocumentsSaving lands ahead of the BulkFileListener dispatch.
|
Codecov Report❌ Patch coverage is
🚀 New features to boost your workflow:
|
|
Hi @Pushpavel 👋 Friendly ping on this one — it fixes external edits to |
|
Thanks — this fixes a real problem (external edits to 1. Data resurrection via the if (db == DEFAULT_AUTO_CP_DB && document.text.isNotBlank()) {
project.service<AutoCpStorage>().reloadFromDisk()
return@runReadAction
}This triggers whenever the in-memory DB equals the default, not only when it was never populated. If a user deletes all problems/solution files, Related: in this branch, a non-blank but unparseable file means 2. Skip self-triggered events Every plugin-initiated save that changes content fires a .filter { it !is VFileContentChangeEvent || !it.isFromSave }3. Don't do disk I/O inside VFS event delivery
Nits
With #1 addressed I'm happy to merge; #2/#3 are cheap robustness wins worth doing in the same pass. This is an AI review, but I think first point makes sense, can you check |
…p save-originated events, defer reload out of VFS event delivery
|
Pushed fixes for all three in b05383a.
Also took the nits: path-string matching instead of |
- bump platformVersion to 2026.1.4 (sinceBuild stays 243) - align JUnit Jupiter/Platform versions with 2026.1 test framework - add PR #170 to changelog Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Motivation
.autocpis loaded once viaby lazyand only ever written to, so any external edit (a script, another editor, an AI tool) gets silently overwritten byAutoCpStorageSaveron the nextsaveAllDocumentsusing stale in-memory state.Change
Register a
BulkFileListener(application level) that calls a newAutoCpStorage.reloadFromDisk()whenever a file named.autocpunder any open project'sbasePathreports a content change or creation. It re-parses through the existingMigrationspath and resets the twoMutableStateFlows. Events originating from the plugin's own document saves are skipped viaisFromSave, and the reload itself is deferred withinvokeLaterso no disk I/O runs inside VFS event delivery.The saver also short-circuits with
if (document.text == newText) returnbeforesetText, which prevents a write→fire→reload loop and avoids spurious modify events.As a safety net for a save racing ahead of the VFS create event: if the in-memory state was never mutated (tracked by an explicit
mutatedflag onAutoCpDatabase, set byupdateProblem/modifySolutionFilesand cleared when state is replaced from disk) and the on-disk file has content, the saver reloads instead of writing — so a default-initialized state can't clobber an externally created.autocp. A database the user intentionally emptied hasmutated = trueand persists normally.External deletion of
.autocpis intentionally ignored: in-memory state survives and the next save recreates the file.The IDE-internal warning panel in
AutoCpFileEditoris unchanged — direct editing inside the IDE remains blocked viaHIDE_DEFAULT_EDITOR.Failure mode
If the new disk content fails to parse (invalid JSON / schema mismatch),
reloadFromDiskcatches andlog.warns, leaving in-memory state untouched. Once the in-memory state has been mutated, the nextsaveAllDocumentsoverwrites the broken file with the previous valid state — equivalent to the current "external edit silently lost" behaviour. If the plugin has nothing to persist (state never populated), it leaves the unparseable file alone rather than clobbering it.