Integrate release recovery, semantic review, coverage and Windows packaging
This commit is contained in:
@@ -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(¤t);
|
||||
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::*;
|
||||
|
||||
Reference in New Issue
Block a user