Preserve diagnostic source authority when recovery removes save notice
This commit is contained in:
@@ -3390,13 +3390,17 @@ impl App {
|
|||||||
}
|
}
|
||||||
self.invalidate_source_rename("Disk recovery changed this source context. Your replacement is retained; close and invoke Rename again.");
|
self.invalidate_source_rename("Disk recovery changed this source context. Your replacement is retained; close and invoke Rename again.");
|
||||||
self.pending_recovery = None;
|
self.pending_recovery = None;
|
||||||
let diagnostics = self
|
// Removing the recovered-save notice changes presentation,
|
||||||
.source_diagnostics
|
// not the source authority of the remaining locations.
|
||||||
.iter()
|
let diagnostics_current = self
|
||||||
.filter(|issue| issue.diagnostic.code != "pending_save")
|
.text_workspace
|
||||||
.cloned()
|
.diagnostic_publication()
|
||||||
.collect();
|
.is_some_and(|publication| publication.source() == self.source_key());
|
||||||
self.publish_source_diagnostics(diagnostics);
|
self.source_diagnostics
|
||||||
|
.retain(|issue| issue.diagnostic.code != "pending_save");
|
||||||
|
if diagnostics_current {
|
||||||
|
self.publish_source_diagnostics(self.source_diagnostics.clone());
|
||||||
|
}
|
||||||
match receipt.disk {
|
match receipt.disk {
|
||||||
Ok(disk) if !self.source_workspace.applied => {
|
Ok(disk) if !self.source_workspace.applied => {
|
||||||
self.open_source_project(disk)
|
self.open_source_project(disk)
|
||||||
|
|||||||
@@ -454,3 +454,158 @@ fn saved_new_destination_does_not_revalidate_already_stale_warning_locations() {
|
|||||||
"receipt records accepted bytes while the newer draft remains unsaved"
|
"receipt records accepted bytes while the newer draft remains unsaved"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use an actual pending-save journal and the real recovery worker. The optional
|
||||||
|
/// reload error is injected only after recovery succeeded, so both receipt arms
|
||||||
|
/// exercise the same matching target without relying on filesystem races.
|
||||||
|
fn recover_warning_fixture(stale: bool, reload_error: bool) {
|
||||||
|
let directory = tempfile::tempdir().unwrap();
|
||||||
|
let root = directory.path().canonicalize().unwrap();
|
||||||
|
let path = root.join("project.arcadia");
|
||||||
|
let journal = root.join(".arcadia-save-project.arcadia.journal");
|
||||||
|
let mut app = fixture();
|
||||||
|
save(&mut app, &path);
|
||||||
|
std::fs::create_dir(&journal).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
journal.join("journal.json"),
|
||||||
|
serde_json::to_vec(&serde_json::json!({
|
||||||
|
"version": 1, "entry": "project.arcadia", "files": [{
|
||||||
|
"path": "project.arcadia", "before": true, "write": true
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
std::fs::write(journal.join("0.before"), VALID).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
journal.join("0.after"),
|
||||||
|
format!("{VALID}// interrupted save\r\n"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let disk = oa_core::source_files::load_project(&path, LIMITS).unwrap();
|
||||||
|
app.pending_recovery = crate::app::recovery_target(&disk);
|
||||||
|
assert_eq!(app.pending_recovery.as_ref(), Some(&path));
|
||||||
|
let pending = disk
|
||||||
|
.diagnostics
|
||||||
|
.into_iter()
|
||||||
|
.find(|issue| issue.diagnostic.code == "pending_save")
|
||||||
|
.unwrap();
|
||||||
|
// Current-warning control includes newer unsaved bytes too. Recovery of an
|
||||||
|
// applied workspace must retain its drafts, not reload over them.
|
||||||
|
let draft = format!("{VALID}// retained draft\r\n");
|
||||||
|
if !stale {
|
||||||
|
app.source_workspace
|
||||||
|
.edit(draft.clone(), app.source_editor.cursor());
|
||||||
|
app.show_source_file(false);
|
||||||
|
}
|
||||||
|
let retained_warning = warning(&app);
|
||||||
|
app.publish_source_diagnostics(vec![retained_warning.clone(), pending]);
|
||||||
|
let show = Message::TextWorkspace(Event::ShowDrawer(
|
||||||
|
app.text_drawer_context(),
|
||||||
|
DrawerTab::Problems,
|
||||||
|
));
|
||||||
|
send(&mut app, show);
|
||||||
|
let old = warning_messages(&app);
|
||||||
|
let publication = app.text_workspace.diagnostic_publication().unwrap();
|
||||||
|
if stale {
|
||||||
|
// This is the existing workspace-key departure seam: drafts advance
|
||||||
|
// the key while a previous diagnostic Vec can still await lint.
|
||||||
|
app.source_workspace
|
||||||
|
.edit(draft.clone(), app.source_editor.cursor());
|
||||||
|
app.show_source_file(false);
|
||||||
|
assert_ne!(publication.source(), app.source_key());
|
||||||
|
} else {
|
||||||
|
assert_eq!(publication.source(), app.source_key());
|
||||||
|
}
|
||||||
|
assert!(app.source_workspace.applied && app.source_workspace.has_edits());
|
||||||
|
reveal(&mut app);
|
||||||
|
let accepted = app.document.snapshot();
|
||||||
|
let revision = app.document.revision();
|
||||||
|
let raw = app.source_workspace.bundle();
|
||||||
|
let key = app.source_key();
|
||||||
|
let cursor = app.source_editor.cursor();
|
||||||
|
let visit = app.text_workspace.drawer_visit();
|
||||||
|
send(&mut app, Message::SourceRecover);
|
||||||
|
assert!(
|
||||||
|
app.busy,
|
||||||
|
"normal recovery request admitted with retained applied drafts"
|
||||||
|
);
|
||||||
|
let mut receipt = crate::app::recover_snapshot(path.clone()).unwrap();
|
||||||
|
assert_eq!(receipt.target, path);
|
||||||
|
assert!(receipt.disk.is_ok());
|
||||||
|
assert!(!journal.exists(), "real recovery consumed the journal");
|
||||||
|
if reload_error {
|
||||||
|
receipt.disk = Err("injected reload failure after successful recovery".into());
|
||||||
|
}
|
||||||
|
send(&mut app, Message::SourceRecovered(Ok(receipt)));
|
||||||
|
assert!(!app.busy && app.pending_recovery.is_none());
|
||||||
|
assert_eq!(app.source_diagnostics, vec![retained_warning]);
|
||||||
|
assert_eq!(app.source_key(), key);
|
||||||
|
assert_eq!(app.source_workspace.bundle(), raw);
|
||||||
|
assert_eq!(app.source_workspace.text(), draft);
|
||||||
|
assert_eq!(app.source_editor.cursor(), cursor);
|
||||||
|
assert_eq!(app.text_workspace.drawer(), Some(DrawerTab::Problems));
|
||||||
|
assert_eq!(app.text_workspace.drawer_visit(), visit);
|
||||||
|
assert!(Arc::ptr_eq(&app.document.snapshot(), &accepted));
|
||||||
|
assert_eq!(app.document.revision(), revision);
|
||||||
|
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||||
|
assert_eq!(std::fs::read_to_string(&path).unwrap(), VALID);
|
||||||
|
assert!(app.status.contains(if reload_error {
|
||||||
|
"files could not be reloaded"
|
||||||
|
} else {
|
||||||
|
"Current model and drafts are retained"
|
||||||
|
}));
|
||||||
|
if stale {
|
||||||
|
assert_eq!(
|
||||||
|
app.text_workspace.diagnostic_publication(),
|
||||||
|
Some(publication)
|
||||||
|
);
|
||||||
|
assert!(app.source_annotation_snapshot().is_none());
|
||||||
|
assert!(
|
||||||
|
app.text_diagnostic_message(SourceLocation {
|
||||||
|
path: app.source_diagnostics[0].path.clone(),
|
||||||
|
span: app.source_diagnostics[0].diagnostic.span,
|
||||||
|
})
|
||||||
|
.is_none(),
|
||||||
|
"recovery must not manufacture a fresh stale-warning action"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let current = app.text_workspace.diagnostic_publication().unwrap();
|
||||||
|
assert_eq!(current.source(), key);
|
||||||
|
assert_ne!(current.generation(), publication.generation());
|
||||||
|
assert!(app.source_annotation_snapshot().is_some());
|
||||||
|
}
|
||||||
|
for message in old {
|
||||||
|
let interaction = app.source_symbols.interaction;
|
||||||
|
send(&mut app, message);
|
||||||
|
assert_eq!(app.source_symbols.interaction, interaction);
|
||||||
|
assert!(app.source_symbols.reveal.is_none() && app.source_jump.is_none());
|
||||||
|
}
|
||||||
|
if !stale {
|
||||||
|
for message in warning_messages(&app) {
|
||||||
|
send(&mut app, message);
|
||||||
|
assert!(
|
||||||
|
app.source_symbols.reveal.is_some(),
|
||||||
|
"current primary and related remain live"
|
||||||
|
);
|
||||||
|
reveal(&mut app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(app.source_workspace.bundle(), raw);
|
||||||
|
assert!(Arc::ptr_eq(&app.document.snapshot(), &accepted));
|
||||||
|
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recovery_retains_current_warning_authority_with_drafts_and_reload_failure() {
|
||||||
|
for reload_error in [false, true] {
|
||||||
|
recover_warning_fixture(false, reload_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recovery_does_not_republish_stale_warnings_after_workspace_key_departure() {
|
||||||
|
for reload_error in [false, true] {
|
||||||
|
recover_warning_fixture(true, reload_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user