Integrate release recovery, semantic review, coverage and Windows packaging

This commit is contained in:
Codex
2026-09-06 16:31:55 -07:00
parent c091f80e6e
commit 2b2d2bc18b
174 changed files with 27635 additions and 127 deletions
+132 -1
View File
@@ -15,6 +15,12 @@ struct HistoryEntry {
bytes: usize,
}
struct RelocatedHistory {
current: Arc<CompiledSourceProject>,
past: Vec<HistoryEntry>,
future: Vec<HistoryEntry>,
}
fn estimated_bytes(compiled: &CompiledSourceProject) -> usize {
compiled
.bundle
@@ -92,6 +98,7 @@ pub struct Document {
saved: Arc<SourceBundle>,
revision: u64,
limits: ProjectLimits,
recovered_unsaved: bool,
}
impl Document {
@@ -120,6 +127,7 @@ impl Document {
saved: Arc::new(bundle),
revision: 0,
limits,
recovered_unsaved: false,
})
}
@@ -140,7 +148,12 @@ impl Document {
}
pub fn dirty(&self) -> bool {
self.bundle() != self.saved.as_ref()
self.recovered_unsaved || self.bundle() != self.saved.as_ref()
}
/// Recovered work is a detached copy until explicitly saved by its user.
pub fn mark_recovered(&mut self) {
self.recovered_unsaved = true;
}
pub fn can_undo(&self) -> bool {
@@ -161,6 +174,119 @@ impl Document {
/// The I/O adapter owns document identity and save completion ordering.
pub fn mark_saved(&mut self, snapshot: Arc<CompiledSourceProject>) {
self.saved = Arc::new(snapshot.bundle.clone());
self.recovered_unsaved = false;
}
/// Check a source Save as before writing any files. A retained included file
/// must never be displaced by the new entry, including on a later Undo.
pub fn check_save_entry(&self, entry: &str) -> Result<(), DocumentError> {
if entry == self.bundle().entry {
return Ok(());
}
self.relocated_history(entry).map(|_| ())
}
/// Adopt a successfully saved source entry without recording an engineering
/// edit or clearing Redo. The caller still marks the exact receipt as saved.
pub fn adopt_saved_entry(
&mut self,
saved: &CompiledSourceProject,
revision: u64,
) -> Result<(), DocumentError> {
if revision != self.revision {
return Err(
ModelError("Newer edits retain the current source location.".into()).into(),
);
}
let RelocatedHistory {
current,
past,
future,
} = self.relocated_history(&saved.bundle.entry)?;
if current.bundle != saved.bundle
|| current.decoded.project != saved.decoded.project
|| current.index != saved.index
{
return Err(ModelError(
"The save receipt changes more than the source entry location.".into(),
)
.into());
}
if current.bundle != self.current.bundle {
self.current_bytes = estimated_bytes(&current);
self.current = current;
self.past = past;
self.future = future;
// Source-position readers must reject their old revision even though
// changing the save location does not create an Undo transaction.
self.revision = self.revision.saturating_add(1);
}
Ok(())
}
fn relocated_history(&self, entry: &str) -> Result<RelocatedHistory, DocumentError> {
if entry.contains('/') || source_project::resolve_include("", entry).as_deref() != Ok(entry)
{
return Err(ModelError("Choose a portable .arcadia entry file name.".into()).into());
}
let snapshots = std::iter::once(self.current.as_ref())
.chain(self.past.iter().map(|h| h.snapshot.as_ref()))
.chain(self.future.iter().map(|h| h.snapshot.as_ref()));
let mut uniform_entry = true;
for snapshot in snapshots {
uniform_entry &= snapshot.bundle.entry == self.bundle().entry;
if snapshot.bundle.files.keys().any(|path| {
path != &snapshot.bundle.entry && path.to_lowercase() == entry.to_lowercase()
}) {
return Err(ModelError(format!(
"The entry name '{entry}' is used by an included file in this document or its Undo/Redo history. Choose another entry name."
)).into());
}
}
let relocate = |snapshot: &Arc<CompiledSourceProject>| -> Result<Arc<CompiledSourceProject>, DocumentError> {
if snapshot.bundle.entry == entry {
return Ok(snapshot.clone());
}
let mut bundle = snapshot.bundle.clone();
let text = bundle
.files
.remove(&bundle.entry)
.ok_or_else(|| ModelError("The accepted source entry is missing.".into()))?;
bundle.entry = entry.to_owned();
bundle.files.insert(entry.to_owned(), text);
let compiled = source_project::compile_bundle(&bundle, self.limits)?;
if compiled.bundle != bundle || compiled.decoded.project != snapshot.decoded.project {
return Err(ModelError(
"Changing the entry location would change the model or authored source.".into(),
)
.into());
}
Ok(Arc::new(compiled))
};
let current = relocate(&self.current)?;
let history = |entries: &[HistoryEntry]| {
entries
.iter()
.map(|old| {
// Different entry names in history are authored source changes.
// Keep those snapshots exact instead of collapsing real edits.
let snapshot = if uniform_entry {
relocate(&old.snapshot)?
} else {
old.snapshot.clone()
};
Ok(HistoryEntry {
bytes: estimated_bytes(&snapshot),
snapshot,
})
})
.collect::<Result<Vec<_>, DocumentError>>()
};
Ok(RelocatedHistory {
current,
past: history(&self.past)?,
future: history(&self.future)?,
})
}
pub fn execute(&mut self, command: Command) -> Result<Option<Id>, DocumentError> {
@@ -178,6 +304,7 @@ impl Document {
Ok(candidate.selected)
}
#[cfg(test)]
pub fn apply_bundle(&mut self, bundle: SourceBundle) -> Result<bool, DocumentError> {
let compiled = source_project::compile_bundle(&bundle, self.limits)?;
Ok(self.commit(compiled))
@@ -259,6 +386,10 @@ impl Document {
}
}
#[cfg(test)]
#[path = "document_save_as_tests.rs"]
mod save_as_tests;
#[cfg(test)]
mod tests {
use super::*;